diff --git a/.coderabbit.yaml b/.coderabbit.yaml index d000d69ee2..175f68340e 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -24,4 +24,4 @@ reviews: - Avoid general concepts and focus on the current code. - Suggest code changes with concrete code rather than explanations. path_filters: - - "!datascience/**" + - "!pipeline/**" diff --git a/.env.dev.defaults b/.env.dev.defaults index 2b37bbc354..831d58c6eb 100644 --- a/.env.dev.defaults +++ b/.env.dev.defaults @@ -82,8 +82,6 @@ POSTGRES_HOST=localhost POSTGRES_SCHEMA=public POSTGRES_PASSWORD=postgres -MONITORENV_DATA_FOLDER=./datascience/src/pipeline/data - ################################################################################ # Monitoring & Backup diff --git a/.env.infra.example b/.env.infra.example index 904376954e..4cd3a633f7 100644 --- a/.env.infra.example +++ b/.env.infra.example @@ -76,8 +76,6 @@ POSTGRES_HOST= POSTGRES_SCHEMA= POSTGRES_PASSWORD= -MONITORENV_DATA_FOLDER= - ################################################################################ # Monitoring & Backup diff --git a/.env.test.defaults b/.env.test.defaults index 85c109009d..b01725aa48 100644 --- a/.env.test.defaults +++ b/.env.test.defaults @@ -70,8 +70,6 @@ POSTGRES_HOST=localhost POSTGRES_SCHEMA=public POSTGRES_PASSWORD=postgres -MONITORENV_DATA_FOLDER=./datascience/src/pipeline/data - ################################################################################ # Monitoring & Backup diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c312f9ac67..1c841f16b0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -31,7 +31,7 @@ updates: update-types: ["minor", "patch"] - package-ecosystem: "pip" - directory: "/datascience" + directory: "/pipeline" assignees: - "VincentAntoine" schedule: @@ -39,10 +39,6 @@ updates: commit-message: prefix: "[Tech] " labels: ["tech. enhancement", "dependencies"] - # Disable all dependencies update - # TODO Re-enable once Env is migrated to Poetry-only. - # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit - open-pull-requests-limit: 0 groups: non-major-dependencies: applies-to: version-updates diff --git a/.github/workflows/cicd-datapipeline.yml b/.github/workflows/cicd-datapipeline.yml deleted file mode 100644 index 58bec6f1a9..0000000000 --- a/.github/workflows/cicd-datapipeline.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: CI/CD data processing pipeline - -on: - push: - paths: - - "backend/src/main/resources/db/migration/**" - - ".github/workflows/cicd-database.yml" - - ".github/workflows/cicd-datapipeline.yml" - - "datascience/**" - - "infra/**" - - "Makefile" - schedule: - - cron: "38 11 */3 * *" - workflow_dispatch: - -jobs: - build: - name: Build & test docker image - runs-on: ubuntu-22.04 - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Get last release version - id: lastrelease - uses: pozetroninc/github-action-get-latest-release@master - with: - repository: mtes-mct/monitorenv - - - name: Set ENV_PROFILE as PROD when it is a release - if: startsWith(github.ref, 'refs/tags/v1') || startsWith(github.ref, 'refs/heads/v1') || startsWith(github.ref, 'refs/tags/v2') || startsWith(github.ref, 'refs/heads/v2') - run: echo "ENV_PROFILE=prod" >> $GITHUB_ENV - - - name: Set VERSION - run: | - if [ "${ENV_PROFILE}" != "prod" ]; then\ - if [ "${{github.ref}}" == "refs/heads/main" ]; then\ - export VERSION=${{ steps.lastrelease.outputs.release }}_snapshot - else\ - export REF_NAME=${{ github.ref_name }} - export VERSION=${REF_NAME//\//_} - fi - else\ - export VERSION=${{ steps.lastrelease.outputs.release }} - fi - echo $VERSION - echo "VERSION=$VERSION" >> $GITHUB_ENV - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@master - - - name: Cache Docker layers - uses: actions/cache@v4 - with: - path: /tmp/.buildx-cache-pipeline - key: ${{ runner.os }}-single-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-single-buildx - - - name: Build image - uses: docker/build-push-action@v6 - with: - context: . - load: true - builder: ${{ steps.buildx.outputs.name }} - file: infra/docker/datapipeline/Dockerfile - push: false - tags: monitorenv-pipeline:${{ env.VERSION }} - cache-from: type=local,src=/tmp/.buildx-cache-pipeline - cache-to: type=local,dest=/tmp/.buildx-cache-pipeline-new - - # Temp fix - # https://github.com/docker/build-push-action/issues/252 - # https://github.com/moby/buildkit/issues/1896 - - name: Move cache - run: | - rm -rf /tmp/.buildx-cache-pipeline - mv /tmp/.buildx-cache-pipeline-new /tmp/.buildx-cache-pipeline - - - name: Set DOCKER_GROUP - run: echo "DOCKER_GROUP=$(getent group docker | cut --delimiter=":" -f3)" >> $GITHUB_ENV - - - name: Test docker image - run: make docker-test-pipeline - - - name: Push docker image to registry - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${GITHUB_ACTOR} --password-stdin - make docker-tag-pipeline - make docker-push-pipeline diff --git a/.gitignore b/.gitignore index f3b02a60d8..703709b5f7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,21 +6,15 @@ github_token.txt /infra/data-pipeline-prefect3/.prefect-worker # Notebooks -/datascience/notebooks /pipeline/notebooks -/datascience/.local /pipeline/.local -/datascience/.ipython /pipeline/.ipython -/datascience/.cache /pipeline/.cache .ipynb_checkpoints/ .jupyter/ # Coverage reports -datascience/htmlcov/ pipeline/htmlcov/ -datascience/.coverage pipeline/.coverage frontend/cypress/screenshots/ frontend/cypress/videos/ @@ -28,15 +22,8 @@ frontend/cypress/videos/ # Local data /.backups /data -/datascience/data /pipeline/src/data/**/* !/pipeline/src/data/**/dummy_control_objectives.csv -/datascience/src/pipeline/data/control_objectives.csv -/datascience/src/pipeline/data/control_unit_resources.csv -/datascience/src/pipeline/data/control_unit_contacts.csv -/datascience/src/pipeline/data/bases.csv -/datascience/src/pipeline/data/area_notes.csv -/datascience/src/pipeline/data/terms_notes.csv # Python *.pyc @@ -48,7 +35,6 @@ frontend/cypress/videos/ # Doc docs/redmine_wiki/* -datascience/docs/build pipeline/docs/build # TypeScript cache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffe9b73d4a..b752729c1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,26 @@ # Contributing -- [Getting Started: Backend \& Frontend](#getting-started-backend--frontend) - - [Requirements](#requirements) - - [First Setup](#first-setup) - - [Local Development](#local-development) -- [Getting Started: Data Pipeline](#getting-started-data-pipeline) - - [Requirements](#requirements-1) - - [First Setup](#first-setup-1) - - [Local Development](#local-development-1) -- [Technical Stack](#technical-stack) -- [Legacy Instructions](#legacy-instructions) - - [Technical Stack (Main Components)](#technical-stack-main-components) - - [Development Environment Setup](#development-environment-setup) - - [Prerequisites](#prerequisites) - - [Configuration](#configuration) - - [Sentry](#sentry) - - [Frontend](#frontend) - - [Linting](#linting) - - [Environment Variables](#environment-variables) - - [Backend](#backend) +- [Contributing](#contributing) + - [Getting Started: Backend \& Frontend](#getting-started-backend--frontend) + - [Requirements](#requirements) + - [First Setup](#first-setup) + - [Local Development](#local-development) + - [Getting Started: Data Pipeline](#getting-started-data-pipeline) + - [Requirements](#requirements-1) + - [First Setup](#first-setup-1) + - [Local Development](#local-development-1) + - [Running a test](#running-a-test) + - [Technical Stack](#technical-stack) + - [Legacy Instructions](#legacy-instructions) + - [Technical Stack (Main Components)](#technical-stack-main-components) + - [Development Environment Setup](#development-environment-setup) + - [Prerequisites](#prerequisites) + - [Configuration](#configuration) + - [Sentry](#sentry) + - [Frontend](#frontend) + - [Linting](#linting) + - [Environment Variables](#environment-variables) + - [Backend](#backend) --- @@ -60,15 +62,16 @@ make dev-run-front - Debian-based Linux or macOS - Docker v25 (with Docker Compose v2) -- Python v3.10 (with Poetry) +- Python 3 (with pyenv and Poetry) if you have a mac with apple chipset please check the checkbox in docker dashboard `Allow the default Docker socket to be used (requires password)` in `settings/advanced` ### First Setup -- install Peotry (follow online documentation, it changes really often) -- run ```cd datascience``` and ```poetry install``` +- install pyenv and Poetry (follow online documentation, it changes really often) +- install the correct python version using ```pyenv install ``` (use version defined in pipeline/.python-version) +- run ```make install-pipeline``` ### Local Development @@ -121,7 +124,7 @@ For example : - OpenLayers - Rsuite - Data pipeline: - - Python 3.10 + - Python 3 - Poetry - Prefect @@ -133,7 +136,7 @@ For example : - openjdk (osx: `brew install openjdk`) - postgres (only `psql` is required. osx: `brew install libpq`) - docker + docker-compose -- python 3.10 + poetry +- python 3 + poetry #### Configuration diff --git a/Makefile b/Makefile index 0da4ca9438..696f8bf826 100644 --- a/Makefile +++ b/Makefile @@ -113,11 +113,9 @@ init-geoserver: # DATA commands .PHONY: install-pipeline run-notebook test-pipeline update-python-dependencies install-pipeline: - cd datascience && poetry install + cd pipeline && poetry install run-notebook: - cd datascience && poetry run jupyter notebook -test-pipeline: - cd datascience && export TEST_LOCAL=True && poetry run coverage run -m pytest --pdb tests/ && poetry run coverage report && poetry run coverage html + cd pipeline && poetry run jupyter notebook test-pipeline-prefect3: cd pipeline && export TEST=True && poetry run coverage run -m pytest -s --pdb tests/ && poetry run coverage report && poetry run coverage html @@ -135,16 +133,6 @@ test: test-back cd frontend && CI=true npm run test:unit # CI commands - pipeline -.PHONY: docker-build-pipeline docker-test-pipeline docker-tag-pipeline docker-push-pipeline -docker-build-pipeline: - docker build -f "infra/docker/datapipeline/Dockerfile" . -t monitorenv-pipeline:$(VERSION) -docker-test-pipeline: - docker run --network host -v /var/run/docker.sock:/var/run/docker.sock -u monitorenv-pipeline:$(DOCKER_GROUP) --env-file datascience/.env.test --env HOST_MIGRATIONS_FOLDER=$(HOST_MIGRATIONS_FOLDER) monitorenv-pipeline:$(VERSION) coverage run -m pytest --pdb tests -docker-tag-pipeline: - docker tag monitorenv-pipeline:$(VERSION) ghcr.io/mtes-mct/monitorenv/monitorenv-pipeline:$(VERSION) -docker-push-pipeline: - docker push ghcr.io/mtes-mct/monitorenv/monitorenv-pipeline:$(VERSION) - .PHONY: docker-build-pipeline-prefect3 docker-test-pipeline-prefect3 docker-tag-pipeline-prefect3 docker-push-pipeline-prefect3 docker-build-pipeline-prefect3: docker build -f "infra/docker/datapipeline/prefect3.Dockerfile" . -t monitorenv-pipeline-prefect3:$(VERSION) diff --git a/datascience/.env.template b/datascience/.env.template deleted file mode 100644 index f7c21e0e3a..0000000000 --- a/datascience/.env.template +++ /dev/null @@ -1,62 +0,0 @@ -# Database secrets -ORACLE_HOST= -ORACLE_PORT= -ORACLE_CLIENT=oracle+cx_oracle - -ORACLE_FMC_SID= -ORACLE_FMC_USER= -ORACLE_FMC_PASSWORD= - -# remote : MAN5 (St Malo) (prod or integration) -MONITORENV_REMOTE_DB_CLIENT=postgresql -MONITORENV_REMOTE_DB_HOST= -MONITORENV_REMOTE_DB_PORT=5432 -MONITORENV_REMOTE_DB_NAME=monitorenvdb -MONITORENV_REMOTE_DB_USER= -MONITORENV_REMOTE_DB_PWD= - -# local : CROSS (idem integration or prod) -MONITORFISH_LOCAL_CLIENT=postgresql -MONITORFISH_LOCAL_HOST= -MONITORFISH_LOCAL_PORT=5432 -MONITORFISH_LOCAL_NAME= -MONITORFISH_LOCAL_USER= -MONITORFISH_LOCAL_PWD= - -# local : CROSS (idem integration or prod) -CACEM_LOCAL_CLIENT=postgresql -CACEM_LOCAL_HOST= -CACEM_LOCAL_PORT=5432 -CACEM_LOCAL_NAME= -CACEM_LOCAL_USER= -CACEM_LOCAL_PWD= - -# Proxies to use when accessing the Internet -HTTP_PROXY_= -HTTPS_PROXY_= - -# Prefect Server endpoint -PREFECT_SERVER_URL= - -# Metabase -METABASE_URL= - -# Email settings -EMAIL_SERVER_URL= -EMAIL_SERVER_PORT= -MONITORENV_SENDER_EMAIL_ADDRESS= - -# Recipients -CACEM_EMAIL_ADDRESS= -CACEM_ANALYST_NAME= -CACEM_ANALYST_EMAIL= - -# Deployment options -IS_INTEGRATION= -TEST_MODE= - -# Email actions to control units flow config -EMAIL_ALL_UNITS= - -# data.gouv.fr -DATAGOUV_API_KEY= \ No newline at end of file diff --git a/datascience/.env.test b/datascience/.env.test deleted file mode 100644 index e711cda89f..0000000000 --- a/datascience/.env.test +++ /dev/null @@ -1,32 +0,0 @@ -MONITORENV_REMOTE_DB_CLIENT=postgresql -MONITORENV_REMOTE_DB_HOST=0.0.0.0 -MONITORENV_REMOTE_DB_PORT=5434 -MONITORENV_REMOTE_DB_NAME=monitorenv_test_db -MONITORENV_REMOTE_DB_USER=postgres -MONITORENV_REMOTE_DB_PWD=postgres - -MONITORENV_URL=https://monitor.fish/ -METABASE_URL=https://meta.base.url - -CACEM_LOCAL_CLIENT=postgresql -CACEM_LOCAL_HOST=0.0.0.0 -CACEM_LOCAL_PORT=5434 -CACEM_LOCAL_NAME=monitorenv_test_db -CACEM_LOCAL_USER=postgres -CACEM_LOCAL_PWD=postgres - -# Proxy settings -HTTPS_PROXY_=http://some.ip.address:port -HTTP_PROXY_=http://some.ip.address:port - -# Email settings -MONITORENV_SENDER_EMAIL_ADDRESS=monitorenv@email.fr - -# Recipients -CACEM_EMAIL_ADDRESS=cacem@email.fr - -CACEM_ANALYST_NAME=Mike -CACEM_ANALYST_EMAIL=mike.data@ana.lyst - -# data.gouv.fr -DATAGOUV_API_KEY=datagouv_api_key \ No newline at end of file diff --git a/datascience/.pre-commit-config.yaml b/datascience/.pre-commit-config.yaml deleted file mode 100644 index a0bc3580cd..0000000000 --- a/datascience/.pre-commit-config.yaml +++ /dev/null @@ -1,36 +0,0 @@ -repos: - - repo: https://github.com/myint/autoflake - rev: v1.4 - hooks: - - id: autoflake - name: autoflake - entry: autoflake - language: python - 'types': [python] - require_serial: true - args: ['--in-place', '--remove-all-unused-imports', '--ignore-init-module-imports'] - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort (python) - - repo: https://github.com/psf/black - rev: 20.8b1 - hooks: - - id: black - additional_dependencies: ['click==8.0.4'] - language_version: python3 - - repo: https://github.com/pycqa/flake8 - rev: 3.8.4 - hooks: - - id: flake8 - args: # arguments to configure flake8 - # making isort line length compatible with black - - "--max-line-length=88" - - "--max-complexity=18" - # - "--select=B,C,E,F,W,T4,B9" - # these are errors that will be ignored by flake8 - # check out their meaning here - # https://flake8.pycqa.org/en/latest/user/error-codes.html - - "--ignore=D203,E203,E266,E501,W503,F403,F401,E402,E712,E722" - - "--exclude=dam-si-jupyter-config.py" diff --git a/datascience/.python-version b/datascience/.python-version deleted file mode 100644 index 56d91d3534..0000000000 --- a/datascience/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10.12 diff --git a/datascience/__init__.py b/datascience/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/config.py b/datascience/config.py deleted file mode 100644 index 9fcdc1f423..0000000000 --- a/datascience/config.py +++ /dev/null @@ -1,139 +0,0 @@ -import os -from pathlib import Path - -from dotenv import load_dotenv - -# Package structure -ROOT_DIRECTORY = Path(__file__).parent -LIBRARY_LOCATION = ROOT_DIRECTORY / Path("src") -QUERIES_LOCATION = LIBRARY_LOCATION / Path("pipeline/queries") -TEST_DATA_LOCATION = ROOT_DIRECTORY / Path("tests/test_data") - -LOCAL_MIGRATIONS_FOLDER = str( - ( - ROOT_DIRECTORY / Path("../backend/src/main/resources/db/migration") - ).resolve() -) -# HOST_MIGRATIONS_FOLDER envirionment variable is needed when running tests in CI to -# mount migrations folder from the host to the database container -HOST_MIGRATIONS_FOLDER = os.getenv( - "HOST_MIGRATIONS_FOLDER", LOCAL_MIGRATIONS_FOLDER -) - -EMAIL_TEMPLATES_LOCATION = LIBRARY_LOCATION / Path("pipeline/emails/templates") -EMAIL_STYLESHEETS_LOCATION = LIBRARY_LOCATION / Path( - "pipeline/emails/stylesheets" -) - -# Must be set to true when running tests locally -TEST_LOCAL = os.getenv("TEST_LOCAL", "False").lower() in ( - "true", - "t", - "yes", - "y", -) -if TEST_LOCAL: - load_dotenv(ROOT_DIRECTORY / ".env.test") - -# Must be set to true when running flows locally -RUN_LOCAL = os.getenv("RUN_LOCAL", "False").lower() in ( - "true", - "t", - "yes", - "y", -) -if RUN_LOCAL: - load_dotenv(ROOT_DIRECTORY / ".env") - -# Must be set to true to avoid external side effects (emails, data.gouv uploads...) in -# integration -IS_INTEGRATION = os.getenv("IS_INTEGRATION", "False").lower() in ( - "true", - "t", - "yes", - "y", -) - -# Must be set to true to send controls data to the CACEM_EMAIL_ADDRESS, and -# not to real email addressees (control units) -TEST_MODE = os.getenv("TEST_MODE", "False").lower() in ( - "true", - "t", - "yes", - "y", -) - -# Flow execution configuration -DOCKER_IMAGE = "ghcr.io/mtes-mct/monitorenv/monitorenv-pipeline" -MONITORENV_VERSION = os.getenv("MONITORENV_VERSION") -FLOWS_LOCATION = Path( - "src/pipeline/flows" -) # relative to the WORKDIR in the image - - -# Proxies for pipeline flows requiring Internet access -PROXIES = { - "http": os.environ.get("HTTP_PROXY_"), - "https": os.environ.get("HTTPS_PROXY_"), -} - -# URLs to fetch data from -FAO_AREAS_URL = ( - "http://www.fao.org/fishery/geoserver/fifao/ows?" - "service=WFS&request=GetFeature&version=1.0.0&" - "typeName=fifao:FAO_AREAS_CWP&outputFormat=SHAPE-ZIP" -) - -AMP_AREAS_URL = ( - "https://wxs.ofb.fr/geoserver/gestion/ows?" - "service=WFS&request=GetFeature&version=2.0.0&" - "typeName=ges_omon_amp_ofb_pol_3857_vue&" - "outputFormat=SHAPE-ZIP" -) -# Prefect Server endpoint -PREFECT_SERVER_URL = os.getenv("PREFECT_SERVER_URL") - -# Metabase -METABASE_URL = os.getenv("METABASE_URL") - -# Historic id ranges -HISTORIC_CONTROL_UNITS_MAX_ID = 9999 - -# Shift ids from Poseidon to MonitorEnv -POSEIDON_CACEM_MISSION_ID_TO_MONITORENV_MISSION_ID_SHIFT = -100000 - -# Email server -EMAIL_SERVER_URL = os.environ.get("EMAIL_SERVER_URL") -EMAIL_SERVER_PORT = os.environ.get("EMAIL_SERVER_PORT") -MONITORENV_SENDER_EMAIL_ADDRESS = os.environ.get( - "MONITORENV_SENDER_EMAIL_ADDRESS" -) - -# Recipients -CACEM_EMAIL_ADDRESS = os.environ.get("CACEM_EMAIL_ADDRESS") - -CACEM_ANALYST_NAME = os.getenv("CACEM_ANALYST_NAME") -CACEM_ANALYST_EMAIL = os.getenv("CACEM_ANALYST_EMAIL") - -# Email actions to control units flow config -EMAIL_ALL_UNITS = os.getenv("EMAIL_ALL_UNITS", "False").lower() in ( - "true", - "t", - "yes", - "y", -) - -# data.gouv.fr configuration -DATAGOUV_API_ENDPOINT = "https://www.data.gouv.fr/api/1" -DATAGOUV_API_KEY = os.getenv("DATAGOUV_API_KEY") - -# data.gouv.fr resource ids -REGULATORY_AREAS_DATASET_ID = "682ae3040ebe621687ec64ad" -REGULATORY_AREAS_CSV_RESOURCE_ID = "c9fe6865-602f-452c-ab31-e1d25222c158" -REGULATORY_AREAS_GEOPACKAGE_RESOURCE_ID = "dd48b545-a1d1-4710-9e56-415b895f5336" -REGULATORY_AREAS_CSV_RESOURCE_TITLE = "regulatory_areas.csv" -REGULATORY_AREAS_GEOPACKAGE_RESOURCE_TITLE = "regulatory_areas.gpkg" - -# Vessel repository XML -VESSEL_FILES_GID = os.getenv("VESSEL_FILES_GID") -VESSEL_FILES_DIRECTORY = "/data/vessel_repository" \ No newline at end of file diff --git a/datascience/main.py b/datascience/main.py deleted file mode 100644 index 45aaefdedf..0000000000 --- a/datascience/main.py +++ /dev/null @@ -1,51 +0,0 @@ -import prefect - -from config import PREFECT_SERVER_URL -from src.pipeline.flows_config import flows_to_register - -PROJECT_NAME = "Monitorenv" - - -def create_project_if_not_exists( - client: prefect.Client, project_name: str -) -> None: - """Checks whether a project named "Monitorenv" already exists in Prefect Server. - If not, the project is created. - - Args: - client (prefect.Client): Prefect client instance - - Raises: - ValueError: if more than 1 project with the name "Monitorenv" are found. - """ - r = client.graphql( - 'query{project(where: {name: {_eq : "Monitorenv"}}){name}}' - ) - projects = r["data"]["project"] - if len(projects) == 0: - print("Monitorenv project does not exists, it will be created.") - client.create_project(project_name) - elif len(projects) == 1: - print("Monitorenv project already exists. Skipping project creation.") - else: - raise ValueError( - "Several projects with the name 'Monitorenv' were found." - ) - - -if __name__ == "__main__": - # Initialize a client, which can interact with the Prefect orchestrator. - # The communication with the orchestrator is done through the Prefect GraphQL API. - # This API is served on localhost:4200. - print("Create client") - client = prefect.Client(api_server=PREFECT_SERVER_URL) - - # Create the project "Monitorenv" in the orchestrator if it does not yet exist - print("Create project") - create_project_if_not_exists(client, PROJECT_NAME) - - # Register all flows - print("Registering flows") - for flow in flows_to_register: - print(f"Registering flow {flow.name}") - client.register(flow, project_name=PROJECT_NAME, no_url=True) diff --git a/datascience/poetry.lock b/datascience/poetry.lock deleted file mode 100644 index 188ebccf88..0000000000 --- a/datascience/poetry.lock +++ /dev/null @@ -1,2857 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "astroid" -version = "2.15.8" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, -] - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""} - -[[package]] -name = "asttokens" -version = "2.4.1" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = "*" -files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, -] - -[package.dependencies] -six = ">=1.12.0" - -[package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - -[[package]] -name = "black" -version = "24.4.2" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2024.2.2" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "click-plugins" -version = "1.1.1" -description = "An extension module for click to enable registering CLI commands via setuptools entry-points." -optional = false -python-versions = "*" -files = [ - {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, - {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, -] - -[package.dependencies] -click = ">=4.0" - -[package.extras] -dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] - -[[package]] -name = "cligj" -version = "0.7.2" -description = "Click params for commmand line interfaces to GeoJSON" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4" -files = [ - {file = "cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"}, - {file = "cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27"}, -] - -[package.dependencies] -click = ">=4.0" - -[package.extras] -test = ["pytest-cov"] - -[[package]] -name = "cloudpickle" -version = "3.0.0" -description = "Pickler class to extend the standard pickle.Pickler functionality" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, - {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.5.3" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, -] - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "croniter" -version = "2.0.5" -description = "croniter provides iteration for datetime object with cron like format" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.6" -files = [ - {file = "croniter-2.0.5-py2.py3-none-any.whl", hash = "sha256:fdbb44920944045cc323db54599b321325141d82d14fa7453bc0699826bbe9ed"}, - {file = "croniter-2.0.5.tar.gz", hash = "sha256:f1f8ca0af64212fbe99b1bee125ee5a1b53a9c1b433968d8bca8817b79d237f3"}, -] - -[package.dependencies] -python-dateutil = "*" -pytz = ">2021.1" - -[[package]] -name = "css-inline" -version = "0.13.0" -description = "High-performance library for inlining CSS into HTML 'style' attributes" -optional = false -python-versions = ">=3.7" -files = [ - {file = "css_inline-0.13.0-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c06b1cb6e5638328e9f1a62f8a0b760914852834d950b7f6812653231c806ded"}, - {file = "css_inline-0.13.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:af66e7dc7e762ab24ebe57f98b7c02ac87cc10457a64b545de923a47694f63f7"}, - {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cbe8e5b1379695183a1700a70ad22877731b22e223ad4b7ddbd38b6e3b18b484"}, - {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c4861ed5c62412a7f1db87195cb94cfdb7b2ae1f1b4e10a31df7f6217e49bda"}, - {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:30d7d79b939df4ef14587998ad1be350410b3c19e697d0a445f99c7734fdf15a"}, - {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_24_armv7l.whl", hash = "sha256:0111e8c300676aa47b99f30667583ec0a70914a63377ceefa008f5ba75853df3"}, - {file = "css_inline-0.13.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c1a8cb1323579bc38e2b9a55fe2ebb9f95dae5774521d1e870a6548069774197"}, - {file = "css_inline-0.13.0-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c69ca8e8fca9c4d655de091c126d9d69de82b66e3182abdfb37db54c9532caac"}, - {file = "css_inline-0.13.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2f843e58e8907614c39f65898ebddb62e893bf49a022f26ac1fa1a7f53591521"}, - {file = "css_inline-0.13.0-cp37-abi3-win32.whl", hash = "sha256:3963e66e20c554798d58759bb282ef763836b4ee2afa2c6e466b2b31d9475f21"}, - {file = "css_inline-0.13.0-cp37-abi3-win_amd64.whl", hash = "sha256:f3403165e511935de3fd702b667fac2b018e080d96ac04918041788ec409f04b"}, - {file = "css_inline-0.13.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:80db07d589361f1ed75c297bd984049918693016c81cf288cc3dc7276653469a"}, - {file = "css_inline-0.13.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:00410f5bf5bbc50d6f0aa474750aa2c9ebdc51b37af60e15d1ced7e539608492"}, - {file = "css_inline-0.13.0-pp310-pypy310_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3814d5060fa874ea88cc7262f0dec79cd83c63d5528fed049434ff4cd725d296"}, - {file = "css_inline-0.13.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:866dba67fc4065981fc60af72f470e58210eaee59b69ba6ac023f948ee5199b5"}, - {file = "css_inline-0.13.0-pp37-pypy37_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:1fa752b54612e38bfc69083fd77de53df2053bd5f1de2a210e37b3347eb9720d"}, - {file = "css_inline-0.13.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f481d2dfa116c9f1a163c2478b6c4c7d035740a680d19b6cb780677148c595f6"}, - {file = "css_inline-0.13.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:206c1cb0700f00a7164d18ce293bdaa4ca6cdfc4640e04ea5724ef1c4937bd64"}, - {file = "css_inline-0.13.0-pp38-pypy38_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:6761771ea8481fdcb00ce25727e08232561a107a8330f012e52276a6fe84e080"}, - {file = "css_inline-0.13.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d7f8399b05d88c4cf48bcea3a9fe4f6cdab9e4def0e608d9c7e9447dd39f2e72"}, - {file = "css_inline-0.13.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:46296fefce679d760cd824cefcdd4f372dbc23232599a99e94612fc46bc0bfbe"}, - {file = "css_inline-0.13.0-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:4c9e4d17a801240e3d23d047f159a997f01c52d441a2529dddb18557068dbf5b"}, - {file = "css_inline-0.13.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:551619187c6c2d58442e5b67917e4254cec552e38750be942a871bc118ee56d6"}, - {file = "css_inline-0.13.0.tar.gz", hash = "sha256:d1a8366df670f7db78f5da9f8d4f500b3a5485bc945ec53e43c976b1626853ee"}, -] - -[[package]] -name = "cx-oracle" -version = "8.3.0" -description = "Python interface to Oracle" -optional = false -python-versions = "*" -files = [ - {file = "cx_Oracle-8.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6a23da225f03f50a81980c61dbd6a358c3575f212ca7f4c22bb65a9faf94f7f"}, - {file = "cx_Oracle-8.3.0-cp310-cp310-win32.whl", hash = "sha256:715a8bbda5982af484ded14d184304cc552c1096c82471dd2948298470e88a04"}, - {file = "cx_Oracle-8.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:07f01608dfb6603a8f2a868fc7c7bdc951480f187df8dbc50f4d48c884874e6a"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4b3afe7a911cebaceda908228d36839f6441cbd38e5df491ec25960562bb01a0"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-win32.whl", hash = "sha256:076ffb71279d6b2dcbf7df028f62a01e18ce5bb73d8b01eab582bf14a62f4a61"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b82e4b165ffd807a2bd256259a6b81b0a2452883d39f987509e2292d494ea163"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b902db61dcdcbbf8dd981f5a46d72fef40c5150c7fc0eb0f0698b462d6eb834e"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-win32.whl", hash = "sha256:4c82ca74442c298ceec56d207450c192e06ecf8ad52eb4aaad0812e147ceabf7"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:54164974d526b76fdefb0b66a42b68e1fca5df78713d0eeb8c1d0047b83f6bcf"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:410747d542e5f94727f5f0e42e9706c772cf9094fb348ce965ab88b3a9e4d2d8"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-win32.whl", hash = "sha256:3baa878597c5fadb2c72f359f548431c7be001e722ce4a4ebdf3d2293a1bb70b"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:de42bdc882abdc5cea54597da27a05593b44143728e5b629ad5d35decb1a2036"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:df412238a9948340591beee9ec64fa62a2efacc0d91107034a7023e2991fba97"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-win32.whl", hash = "sha256:70d3cf030aefd71f99b45beba77237b2af448adf5e26be0db3d0d3dee6ea4230"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf01ce87edb4ef663b2e5bd604e1e0154d2cc2f12b60301f788b569d9db8a900"}, - {file = "cx_Oracle-8.3.0.tar.gz", hash = "sha256:3b2d215af4441463c97ea469b9cc307460739f89fdfa8ea222ea3518f1a424d9"}, -] - -[[package]] -name = "dask" -version = "2024.5.1" -description = "Parallel PyData with Task Scheduling" -optional = false -python-versions = ">=3.9" -files = [ - {file = "dask-2024.5.1-py3-none-any.whl", hash = "sha256:af1cadd1fd1d1d44600ff5de43dd029e5668fdf87422131f4e3e3aa2a6a63555"}, - {file = "dask-2024.5.1.tar.gz", hash = "sha256:e071fda67031c314569e37ca70b3e88bb30f1d91ff8ee4122b541845847cc264"}, -] - -[package.dependencies] -click = ">=8.1" -cloudpickle = ">=1.5.0" -fsspec = ">=2021.09.0" -importlib-metadata = {version = ">=4.13.0", markers = "python_version < \"3.12\""} -packaging = ">=20.0" -partd = ">=1.2.0" -pyyaml = ">=5.3.1" -toolz = ">=0.10.0" - -[package.extras] -array = ["numpy (>=1.21)"] -complete = ["dask[array,dataframe,diagnostics,distributed]", "lz4 (>=4.3.2)", "pyarrow (>=7.0)", "pyarrow-hotfix"] -dataframe = ["dask-expr (>=1.1,<1.2)", "dask[array]", "pandas (>=1.3)"] -diagnostics = ["bokeh (>=2.4.2)", "jinja2 (>=2.10.3)"] -distributed = ["distributed (==2024.5.1)"] -test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist"] - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -optional = false -python-versions = ">=3.5" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] - -[[package]] -name = "dill" -version = "0.3.8" -description = "serialize all of Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "distributed" -version = "2024.5.1" -description = "Distributed scheduler for Dask" -optional = false -python-versions = ">=3.9" -files = [ - {file = "distributed-2024.5.1-py3-none-any.whl", hash = "sha256:cf77583ce67f6696aa716c30f1cba3403ac866241c00d6e9a0bc1500c6818b40"}, - {file = "distributed-2024.5.1.tar.gz", hash = "sha256:c4e641e5fc014de3b43c584c70f703a7d44557b51b1143db812b8bc861aa84e2"}, -] - -[package.dependencies] -click = ">=8.0" -cloudpickle = ">=1.5.0" -dask = "2024.5.1" -jinja2 = ">=2.10.3" -locket = ">=1.0.0" -msgpack = ">=1.0.0" -packaging = ">=20.0" -psutil = ">=5.7.2" -pyyaml = ">=5.3.1" -sortedcontainers = ">=2.0.5" -tblib = ">=1.6.0" -toolz = ">=0.10.0" -tornado = ">=6.0.4" -urllib3 = ">=1.24.3" -zict = ">=3.0.0" - -[[package]] -name = "docker" -version = "7.1.0" -description = "A Python library for the Docker Engine API." -optional = false -python-versions = ">=3.8" -files = [ - {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, - {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, -] - -[package.dependencies] -pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} -requests = ">=2.26.0" -urllib3 = ">=1.26.0" - -[package.extras] -dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] -docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] -ssh = ["paramiko (>=2.4.3)"] -websockets = ["websocket-client (>=1.3.0)"] - -[[package]] -name = "exceptiongroup" -version = "1.2.1" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "executing" -version = "2.0.1" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = ">=3.5" -files = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, -] - -[package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - -[[package]] -name = "filelock" -version = "3.14.0" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "fiona" -version = "1.9.6" -description = "Fiona reads and writes spatial data files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "fiona-1.9.6-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:63e528b5ea3d8b1038d788e7c65117835c787ba7fdc94b1b42f09c2cbc0aaff2"}, - {file = "fiona-1.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:918bd27d8625416672e834593970f96dff63215108f81efb876fe5c0bc58a3b4"}, - {file = "fiona-1.9.6-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:e313210b30d09ed8f829bf625599e248dadd78622728030221f6526580ff26c5"}, - {file = "fiona-1.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:89095c2d542325ee45894b8837e8048cdbb2f22274934e1be3b673ca628010d7"}, - {file = "fiona-1.9.6-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:98cea6f435843b2119731c6b0470e5b7386aa16b6aa7edabbf1ed93aefe029c3"}, - {file = "fiona-1.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4230eccbd896a79d1ebfa551d84bf90f512f7bcbe1ca61e3f82231321f1a532"}, - {file = "fiona-1.9.6-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:48b6218224e96de5e36b5eb259f37160092260e5de0dcd82ca200b1887aa9884"}, - {file = "fiona-1.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:c1dd5fbc29b7303bb87eb683455e8451e1a53bb8faf20ef97fdcd843c9e4a7f6"}, - {file = "fiona-1.9.6-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:42d8a0e5570948d3821c493b6141866d9a4d7a64edad2be4ecbb89f81904baac"}, - {file = "fiona-1.9.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39819fb8f5ec6d9971cb01b912b4431615a3d3f50c83798565d8ce41917930db"}, - {file = "fiona-1.9.6-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:9b53034efdf93ada9295b081e6a8280af7c75496a20df82d4c2ca46d65b85905"}, - {file = "fiona-1.9.6-cp312-cp312-win_amd64.whl", hash = "sha256:1dcd6eca7524535baf2a39d7981b4a46d33ae28c313934a7c3eae62eecf9dfa5"}, - {file = "fiona-1.9.6-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e5404ed08c711489abcb3a50a184816825b8af06eb73ad2a99e18b8e7b47c96a"}, - {file = "fiona-1.9.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:53bedd2989e255df1bf3378ae9c06d6d241ec273c280c544bb44ffffebb97fb0"}, - {file = "fiona-1.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:77653a08564a44e634c44cd74a068d2f55d1d4029edd16d1c8aadcc4d8cc1d2c"}, - {file = "fiona-1.9.6-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:e7617563b36d2be99f048f0d0054b4d765f4aae454398f88f19de9c2c324b7f8"}, - {file = "fiona-1.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:50037c3b7a5f6f434b562b5b1a5b664f1caa7a4383b00af23cdb59bfc6ba852c"}, - {file = "fiona-1.9.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:bf51846ad602757bf27876f458c5c9f14b09421fac612f64273cc4e3fcabc441"}, - {file = "fiona-1.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:11af1afc1255642a7787fe112c29d01f968f1053e4d4700fc6f3bb879c1622e0"}, - {file = "fiona-1.9.6-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:52e8fec650b72fc5253d8f86b63859acc687182281c29bfacd3930496cf982d1"}, - {file = "fiona-1.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9b92aa1badb2773e7cac19bef3064d73e9d80c67c42f0928db2520a04be6f2f"}, - {file = "fiona-1.9.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:0eaffbf3bfae9960484c0c08ea461b0c40e111497f04e9475ebf15ac7a22d9dc"}, - {file = "fiona-1.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f1b49d51a744874608b689f029766aa1e078dd72e94b44cf8eeef6d7bd2e9051"}, - {file = "fiona-1.9.6.tar.gz", hash = "sha256:791b3494f8b218c06ea56f892bd6ba893dfa23525347761d066fb7738acda3b1"}, -] - -[package.dependencies] -attrs = ">=19.2.0" -certifi = "*" -click = ">=8.0,<9.0" -click-plugins = ">=1.0" -cligj = ">=0.5" -six = "*" - -[package.extras] -all = ["fiona[calc,s3,test]"] -calc = ["shapely"] -s3 = ["boto3 (>=1.3.1)"] -test = ["fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] - -[[package]] -name = "fsspec" -version = "2024.5.0" -description = "File-system specification" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fsspec-2024.5.0-py3-none-any.whl", hash = "sha256:e0fdbc446d67e182f49a70b82cf7889028a63588fde6b222521f10937b2b670c"}, - {file = "fsspec-2024.5.0.tar.gz", hash = "sha256:1d021b0b0f933e3b3029ed808eb400c08ba101ca2de4b3483fbc9ca23fcee94a"}, -] - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -arrow = ["pyarrow (>=1)"] -dask = ["dask", "distributed"] -dev = ["pre-commit", "ruff"] -dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] -fuse = ["fusepy"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -gui = ["panel"] -hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] -libarchive = ["libarchive-c"] -oci = ["ocifs"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] -test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] -test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] -test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] -tqdm = ["tqdm"] - -[[package]] -name = "geoalchemy2" -version = "0.13.3" -description = "Using SQLAlchemy with Spatial Databases" -optional = false -python-versions = ">=3.7" -files = [ - {file = "GeoAlchemy2-0.13.3-py3-none-any.whl", hash = "sha256:a29f98cbe2527284d2374a0aa6f7bf31fb7babb61bc56b7e557afdb21610827a"}, - {file = "GeoAlchemy2-0.13.3.tar.gz", hash = "sha256:d85a7deaa9a230901733f7419d6bff5674b8730651de1a07f2b6423aa4925d09"}, -] - -[package.dependencies] -packaging = "*" -SQLAlchemy = ">=1.4" - -[[package]] -name = "geopandas" -version = "0.13.2" -description = "Geographic pandas extensions" -optional = false -python-versions = ">=3.8" -files = [ - {file = "geopandas-0.13.2-py3-none-any.whl", hash = "sha256:101cfd0de54bcf9e287a55b5ea17ebe0db53a5e25a28bacf100143d0507cabd9"}, - {file = "geopandas-0.13.2.tar.gz", hash = "sha256:e5b56d9c20800c77bcc0c914db3f27447a37b23b2cd892be543f5001a694a968"}, -] - -[package.dependencies] -fiona = ">=1.8.19" -packaging = "*" -pandas = ">=1.1.0" -pyproj = ">=3.0.1" -shapely = ">=1.7.1" - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "identify" -version = "2.5.36" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "importlib-metadata" -version = "7.1.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "importlib-resources" -version = "6.4.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, - {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "ipython" -version = "8.26.0" -description = "IPython: Productive Interactive Computing" -optional = false -python-versions = ">=3.10" -files = [ - {file = "ipython-8.26.0-py3-none-any.whl", hash = "sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff"}, - {file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt-toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack-data = "*" -traitlets = ">=5.13.0" -typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} - -[package.extras] -all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] -kernel = ["ipykernel"] -matplotlib = ["matplotlib"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jedi" -version = "0.19.1" -description = "An autocompletion tool for Python that can be used for text editors." -optional = false -python-versions = ">=3.6" -files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, -] - -[package.dependencies] -parso = ">=0.8.3,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lazy-object-proxy" -version = "1.10.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.8" -files = [ - {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, - {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, -] - -[[package]] -name = "locket" -version = "1.0.0" -description = "File-based locks for Python on Linux and Windows" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3"}, - {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, -] - -[[package]] -name = "lxml" -version = "6.0.2" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -optional = false -python-versions = ">=3.8" -files = [ - {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388"}, - {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c"}, - {file = "lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b"}, - {file = "lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0"}, - {file = "lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5"}, - {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607"}, - {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7"}, - {file = "lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46"}, - {file = "lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078"}, - {file = "lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285"}, - {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456"}, - {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322"}, - {file = "lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849"}, - {file = "lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f"}, - {file = "lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6"}, - {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77"}, - {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314"}, - {file = "lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2"}, - {file = "lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7"}, - {file = "lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf"}, - {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe"}, - {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c"}, - {file = "lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b"}, - {file = "lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed"}, - {file = "lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8"}, - {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d"}, - {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f"}, - {file = "lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312"}, - {file = "lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca"}, - {file = "lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c"}, - {file = "lxml-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a656ca105115f6b766bba324f23a67914d9c728dafec57638e2b92a9dcd76c62"}, - {file = "lxml-6.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c54d83a2188a10ebdba573f16bd97135d06c9ef60c3dc495315c7a28c80a263f"}, - {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:1ea99340b3c729beea786f78c38f60f4795622f36e305d9c9be402201efdc3b7"}, - {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af85529ae8d2a453feee4c780d9406a5e3b17cee0dd75c18bd31adcd584debc3"}, - {file = "lxml-6.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fe659f6b5d10fb5a17f00a50eb903eb277a71ee35df4615db573c069bcf967ac"}, - {file = "lxml-6.0.2-cp38-cp38-win32.whl", hash = "sha256:5921d924aa5468c939d95c9814fa9f9b5935a6ff4e679e26aaf2951f74043512"}, - {file = "lxml-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:0aa7070978f893954008ab73bb9e3c24a7c56c054e00566a21b553dc18105fca"}, - {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694"}, - {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb"}, - {file = "lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f"}, - {file = "lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304"}, - {file = "lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e"}, - {file = "lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62"}, -] - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html-clean = ["lxml_html_clean"] -html5 = ["html5lib"] -htmlsoup = ["BeautifulSoup4"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "marshmallow" -version = "3.21.2" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -optional = false -python-versions = ">=3.8" -files = [ - {file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, - {file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, -] - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "marshmallow-oneofschema" -version = "3.1.1" -description = "marshmallow multiplexing schema" -optional = false -python-versions = ">=3.8" -files = [ - {file = "marshmallow_oneofschema-3.1.1-py3-none-any.whl", hash = "sha256:ff4cb2a488785ee8edd521a765682c2c80c78b9dc48894124531bdfa1ec9303b"}, - {file = "marshmallow_oneofschema-3.1.1.tar.gz", hash = "sha256:68b4a57d0281a04ac25d4eb7a4c5865a57090a0a8fd30fd6362c8e833ac6a6d9"}, -] - -[package.dependencies] -marshmallow = ">=3.0.0,<4.0.0" - -[package.extras] -dev = ["marshmallow-oneofschema[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -tests = ["pytest"] - -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -description = "Inline Matplotlib backend for Jupyter" -optional = false -python-versions = ">=3.8" -files = [ - {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, - {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, -] - -[package.dependencies] -traitlets = "*" - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "msgpack" -version = "1.0.8" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, -] - -[[package]] -name = "mypy" -version = "1.10.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, - {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, - {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, - {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, - {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, - {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, - {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, - {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, - {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, - {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, - {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, - {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, - {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, - {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, - {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, - {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, - {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, - {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, - {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nodeenv" -version = "1.9.0" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "nodeenv-1.9.0-py2.py3-none-any.whl", hash = "sha256:508ecec98f9f3330b636d4448c0f1a56fc68017c68f1e7857ebc52acf0eb879a"}, - {file = "nodeenv-1.9.0.tar.gz", hash = "sha256:07f144e90dae547bf0d4ee8da0ee42664a42a04e02ed68e06324348dafe4bdb1"}, -] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "packaging" -version = "24.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, -] - -[[package]] -name = "pandas" -version = "2.2.2" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, - {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, - {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, - {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, - {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, - {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, -] - -[package.dependencies] -numpy = {version = ">=1.22.4", markers = "python_version < \"3.11\""} -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 = "parso" -version = "0.8.4" -description = "A Python Parser" -optional = false -python-versions = ">=3.6" -files = [ - {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, - {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, -] - -[package.extras] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["docopt", "pytest"] - -[[package]] -name = "partd" -version = "1.4.2" -description = "Appendable key-value storage" -optional = false -python-versions = ">=3.9" -files = [ - {file = "partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f"}, - {file = "partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c"}, -] - -[package.dependencies] -locket = "*" -toolz = "*" - -[package.extras] -complete = ["blosc", "numpy (>=1.20.0)", "pandas (>=1.3)", "pyzmq"] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pendulum" -version = "3.0.0" -description = "Python datetimes made easy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd"}, - {file = "pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a"}, - {file = "pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e"}, - {file = "pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4"}, - {file = "pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"}, - {file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"}, - {file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d4e2512f4e1a4670284a153b214db9719eb5d14ac55ada5b76cbdb8c5c00399d"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3d897eb50883cc58d9b92f6405245f84b9286cd2de6e8694cb9ea5cb15195a32"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e169cc2ca419517f397811bbe4589cf3cd13fca6dc38bb352ba15ea90739ebb"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17c3084a4524ebefd9255513692f7e7360e23c8853dc6f10c64cc184e1217ab"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826d6e258052715f64d05ae0fc9040c0151e6a87aae7c109ba9a0ed930ce4000"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2aae97087872ef152a0c40e06100b3665d8cb86b59bc8471ca7c26132fccd0f"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac65eeec2250d03106b5e81284ad47f0d417ca299a45e89ccc69e36130ca8bc7"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5346d08f3f4a6e9e672187faa179c7bf9227897081d7121866358af369f44f9"}, - {file = "pendulum-3.0.0-cp37-none-win_amd64.whl", hash = "sha256:235d64e87946d8f95c796af34818c76e0f88c94d624c268693c85b723b698aa9"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16"}, - {file = "pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc"}, - {file = "pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5acb1d386337415f74f4d1955c4ce8d0201978c162927d07df8eb0692b2d8533"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a789e12fbdefaffb7b8ac67f9d8f22ba17a3050ceaaa635cd1cc4645773a4b1e"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:860aa9b8a888e5913bd70d819306749e5eb488e6b99cd6c47beb701b22bdecf5"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5ebc65ea033ef0281368217fbf59f5cb05b338ac4dd23d60959c7afcd79a60a0"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9fef18ab0386ef6a9ac7bad7e43ded42c83ff7ad412f950633854f90d59afa8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b"}, - {file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"}, -] - -[package.dependencies] -python-dateutil = ">=2.6" -tzdata = ">=2020.1" - -[package.extras] -test = ["time-machine (>=2.6.0)"] - -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -optional = false -python-versions = "*" -files = [ - {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, - {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "3.7.1" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, - {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "prefect" -version = "1.4.1" -description = "The Prefect Core automation and scheduling engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "prefect-1.4.1-py3-none-any.whl", hash = "sha256:a838d427a88845b13279b89b925e2b6acde5ff2bb090c5480617bc6047a808a8"}, - {file = "prefect-1.4.1.tar.gz", hash = "sha256:179f179849286bb8dc0309c8718a7815e6e5fcc016398d2aea45e16fa0e3471b"}, -] - -[package.dependencies] -click = ">=7.0" -cloudpickle = ">=1.3.0" -croniter = ">=0.3.24" -dask = ">=2021.06.0" -distributed = ">=2.17.0" -docker = ">=3.4.1" -importlib-resources = ">=3.0.0" -marshmallow = ">=3.0.0b19" -marshmallow-oneofschema = ">=2.0.0b2" -msgpack = ">=0.6.0" -mypy-extensions = ">=0.4.0" -packaging = ">=20.0" -pendulum = ">=2.0.4" -python-box = ">=5.1.0" -python-dateutil = ">=2.7.0" -python-slugify = ">=1.2.6" -pytz = ">=2018.7" -pyyaml = ">=3.13" -requests = ">=2.25" -tabulate = ">=0.8.0" -toml = ">=0.9.4" -urllib3 = ">=1.26.0" - -[package.extras] -airtable = ["airtable-python-wrapper (>=0.11)"] -all-extras = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "airtable-python-wrapper (>=0.11)", "atlassian-python-api (>=2.0.1)", "azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)", "azureml-sdk", "black", "boto3 (>=1.9)", "confluent-kafka (>=1.7.0)", "dask-cloudprovider[aws] (>=0.2.0)", "dask-kubernetes (>=0.8.0)", "dropbox (>=9.0,<10.0)", "dulwich (>=0.19.7)", "feedparser (>=5.0.1)", "firebolt-sdk (>=0.2.1)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "graphviz (>=0.8.3)", "great-expectations (>=0.13.8)", "gspread (>=3.6.0)", "hvac (>=0.10)", "ipykernel (>=6.9.2)", "jinja2 (>=2.0)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "nbconvert (>=6.0.7)", "pandas (>=1.0.1)", "papermill (>=2.2.0)", "paramiko (>=2.10.4)", "prometheus-client (>=0.9.0)", "psycopg2-binary (>=2.8.2)", "pushbullet.py (>=0.11.0)", "py2neo (>=2021.2.3)", "pyarrow (>=5.0.0)", "pydantic (>=1.9.0)", "pyexasol (>=0.16.1)", "pymysql (>=0.9.3)", "pyodbc (>=4.0.30)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "redis (>=3.2.1)", "responses (>=0.14.0)", "sendgrid (>=6.7.0)", "snowflake-connector-python (>=1.8.2)", "soda-spark (>=0.2.1)", "soda-sql (>=2.0.0b25)", "spacy (>=2.0.0)", "sqlalchemy-redshift (>=0.8.11)", "testfixtures (>=6.10.3)", "toloka-kit (>=0.1.25)", "transform (>=1.0.12)", "tweepy (>=3.5)"] -all-orchestration-extras = ["PyGithub (>=1.51)", "atlassian-python-api (>=2.0.1)", "azure-identity (>=1.7.0)", "azure-storage-blob (>=12.1.0)", "boto3 (>=1.9)", "dulwich (>=0.19.7)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "kubernetes (>=9.0.0a1)", "python-gitlab (>=2.5.0)"] -aws = ["boto3 (>=1.9)"] -azure = ["azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)"] -azureml = ["azureml-sdk"] -base-library-ci = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "atlassian-python-api (>=2.0.1)", "azure-identity (>=1.7.0)", "azure-storage-blob (>=12.1.0)", "black", "boto3 (>=1.9)", "dulwich (>=0.19.7)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "pandas (>=1.0.1)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "responses (>=0.14.0)", "testfixtures (>=6.10.3)"] -bitbucket = ["atlassian-python-api (>=2.0.1)"] -cubejs = ["PyJWT (>=2.3.0)"] -dask-cloudprovider = ["dask-cloudprovider[aws] (>=0.2.0)"] -databricks = ["pydantic (>=1.9.0)"] -dev = ["PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "black", "flaky (>=3.0)", "freezegun (>=1.0.0)", "graphviz (>=0.8)", "jinja2 (>=2.0,<4.0)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "responses (>=0.14.0)", "testfixtures (>=6.10.3)"] -dremio = ["pyarrow (>=5.0.0)"] -dropbox = ["dropbox (>=9.0,<10.0)"] -exasol = ["pyexasol (>=0.16.1)"] -firebolt = ["firebolt-sdk (>=0.2.1)"] -gcp = ["google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)"] -ge = ["great-expectations (>=0.13.8)", "sqlalchemy-redshift (>=0.8.11)"] -git = ["dulwich (>=0.19.7)"] -github = ["PyGithub (>=1.51)"] -gitlab = ["python-gitlab (>=2.5.0)"] -google = ["google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)"] -gsheets = ["gspread (>=3.6.0)"] -jira = ["jira (>=2.0.0)"] -jupyter = ["ipykernel (>=6.9.2)", "nbconvert (>=6.0.7)", "papermill (>=2.2.0)"] -kafka = ["confluent-kafka (>=1.7.0)"] -kubernetes = ["dask-kubernetes (>=0.8.0)", "kubernetes (>=9.0.0a1)"] -mysql = ["pymysql (>=0.9.3)"] -neo4j = ["py2neo (>=2021.2.3)"] -pandas = ["pandas (>=1.0.1)"] -postgres = ["psycopg2-binary (>=2.8.2)"] -prometheus = ["prometheus-client (>=0.9.0)"] -pushbullet = ["pushbullet.py (>=0.11.0)"] -redis = ["redis (>=3.2.1)"] -rss = ["feedparser (>=5.0.1)"] -sendgrid = ["sendgrid (>=6.7.0)"] -sftp = ["paramiko (>=2.10.4)"] -snowflake = ["snowflake-connector-python (>=1.8.2)"] -sodaspark = ["soda-spark (>=0.2.1)"] -sodasql = ["soda-sql (>=2.0.0b25)"] -spacy = ["spacy (>=2.0.0)"] -sql-server = ["pyodbc (>=4.0.30)"] -task-library-ci = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "airtable-python-wrapper (>=0.11)", "atlassian-python-api (>=2.0.1)", "azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)", "azureml-sdk", "black", "boto3 (>=1.9)", "confluent-kafka (>=1.7.0)", "dask-kubernetes (>=0.8.0)", "dropbox (>=9.0,<10.0)", "dulwich (>=0.19.7)", "feedparser (>=5.0.1)", "firebolt-sdk (>=0.2.1)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "graphviz (>=0.8.3)", "great-expectations (>=0.13.8)", "gspread (>=3.6.0)", "hvac (>=0.10)", "ipykernel (>=6.9.2)", "jinja2 (>=2.0)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "nbconvert (>=6.0.7)", "pandas (>=1.0.1)", "papermill (>=2.2.0)", "paramiko (>=2.10.4)", "prometheus-client (>=0.9.0)", "psycopg2-binary (>=2.8.2)", "pushbullet.py (>=0.11.0)", "py2neo (>=2021.2.3)", "pyarrow (>=5.0.0)", "pydantic (>=1.9.0)", "pyexasol (>=0.16.1)", "pymysql (>=0.9.3)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "redis (>=3.2.1)", "responses (>=0.14.0)", "sendgrid (>=6.7.0)", "snowflake-connector-python (>=1.8.2)", "spacy (>=2.0.0)", "sqlalchemy-redshift (>=0.8.11)", "testfixtures (>=6.10.3)", "toloka-kit (>=0.1.25)", "transform (>=1.0.12)", "tweepy (>=3.5)"] -templates = ["jinja2 (>=2.0)"] -test = ["PyJWT (>=2.3.0)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "responses (>=0.14.0)", "testfixtures (>=6.10.3)"] -toloka = ["toloka-kit (>=0.1.25)"] -transform = ["transform (>=1.0.12)"] -twitter = ["tweepy (>=3.5)"] -vault = ["hvac (>=0.10)"] -viz = ["graphviz (>=0.8.3)"] - -[[package]] -name = "prompt-toolkit" -version = "3.0.47" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "psutil" -version = "5.9.8" -description = "Cross-platform lib for process and system monitoring in Python." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, - {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, - {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, - {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, - {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, - {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, - {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, - {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, - {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, - {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, -] - -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] - -[[package]] -name = "psycopg2-binary" -version = "2.9.9" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, -] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -optional = false -python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -description = "Safely evaluate AST nodes without side effects" -optional = false -python-versions = "*" -files = [ - {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, - {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pylint" -version = "2.17.7" -description = "python code static checker" -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, -] - -[package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = {version = ">=0.2", markers = "python_version < \"3.11\""} -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pyproj" -version = "3.6.1" -description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pyproj-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4"}, - {file = "pyproj-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe"}, - {file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f"}, - {file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35"}, - {file = "pyproj-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f"}, - {file = "pyproj-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e"}, - {file = "pyproj-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882"}, - {file = "pyproj-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a"}, - {file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85"}, - {file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915"}, - {file = "pyproj-3.6.1-cp311-cp311-win32.whl", hash = "sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132"}, - {file = "pyproj-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2"}, - {file = "pyproj-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746"}, - {file = "pyproj-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8"}, - {file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf"}, - {file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d"}, - {file = "pyproj-3.6.1-cp312-cp312-win32.whl", hash = "sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb"}, - {file = "pyproj-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0"}, - {file = "pyproj-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b"}, - {file = "pyproj-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3"}, - {file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1"}, - {file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae"}, - {file = "pyproj-3.6.1-cp39-cp39-win32.whl", hash = "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877"}, - {file = "pyproj-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f"}, - {file = "pyproj-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f"}, - {file = "pyproj-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110"}, - {file = "pyproj-3.6.1.tar.gz", hash = "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf"}, -] - -[package.dependencies] -certifi = "*" - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-box" -version = "7.1.1" -description = "Advanced Python dictionaries with dot notation access" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-box-7.1.1.tar.gz", hash = "sha256:2a3df244a5a79ac8f8447b5d11b5be0f2747d7b141cb2866060081ae9b53cc50"}, - {file = "python_box-7.1.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:81ed1ec0f0ff2370227fc07277c5baca46d190a4747631bad7eb6ab1630fb7d9"}, - {file = "python_box-7.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8891735b4148e84d348c6eadd2f127152f751c9603e35d43a1f496183a291ac4"}, - {file = "python_box-7.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:0036fd47d388deaca8ebd65aea905f88ee6ef91d1d8ce34898b66f1824afbe80"}, - {file = "python_box-7.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aabf8b9ae5dbc8ba431d8cbe0d4cfe737a25d52d68b0f5f2ff34915c21a2c1db"}, - {file = "python_box-7.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c046608337e723ae4de3206db5d1e1202ed166da2dfdc70c1f9361e72ace5633"}, - {file = "python_box-7.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9266795e9c233874fb5b34fa994054b4fb0371881678e6ec45aec17fc95feac"}, - {file = "python_box-7.1.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:f76b5b7f0cdc07bfdd4200dc24e6e33189bb2ae322137a2b7110fd41891a3157"}, - {file = "python_box-7.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ea13c98e05a3ec0ff26f254986a17290b69b5ade209fad081fd628f8fcfaa08"}, - {file = "python_box-7.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3f346e332dba16df0b0543d319d9e7ce07d93e5ae152175302894352aa2d28"}, - {file = "python_box-7.1.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:24c4ec0ee0278f66321100aaa9c615413da27a14ff43d376a2a3b4665e1d9494"}, - {file = "python_box-7.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d95e5eec4fc8f3fc5c9cc7347fc2eb4f9187c853d34c90b1658d1eff96cd4eac"}, - {file = "python_box-7.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:a0f1333c42e81529b6f68c192050df9d4505b803be7ac47f114036b98707f7cf"}, - {file = "python_box-7.1.1-py3-none-any.whl", hash = "sha256:63b609555554d7a9d4b6e725f8e78ef1717c67e7d386200e03422ad612338df8"}, -] - -[package.extras] -all = ["msgpack", "ruamel.yaml (>=0.17)", "toml"] -msgpack = ["msgpack"] -pyyaml = ["PyYAML"] -ruamel-yaml = ["ruamel.yaml (>=0.17)"] -toml = ["toml"] -tomli = ["tomli", "tomli-w"] -yaml = ["ruamel.yaml (>=0.17)"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-slugify" -version = "8.0.4" -description = "A Python slugify application that also handles Unicode" -optional = false -python-versions = ">=3.7" -files = [ - {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, - {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, -] - -[package.dependencies] -text-unidecode = ">=1.3" - -[package.extras] -unidecode = ["Unidecode (>=1.1.1)"] - -[[package]] -name = "pytz" -version = "2023.4" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, - {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, -] - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "shapely" -version = "2.0.4" -description = "Manipulation and analysis of geometric objects" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f"}, - {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17"}, - {file = "shapely-2.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c4849916f71dc44e19ed370421518c0d86cf73b26e8656192fcfcda08218fbd"}, - {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841f93a0e31e4c64d62ea570d81c35de0f6cea224568b2430d832967536308e6"}, - {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b4431f522b277c79c34b65da128029a9955e4481462cbf7ebec23aab61fc58"}, - {file = "shapely-2.0.4-cp310-cp310-win32.whl", hash = "sha256:92a41d936f7d6743f343be265ace93b7c57f5b231e21b9605716f5a47c2879e7"}, - {file = "shapely-2.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:30982f79f21bb0ff7d7d4a4e531e3fcaa39b778584c2ce81a147f95be1cd58c9"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de0205cb21ad5ddaef607cda9a3191eadd1e7a62a756ea3a356369675230ac35"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d56ce3e2a6a556b59a288771cf9d091470116867e578bebced8bfc4147fbfd7"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58b0ecc505bbe49a99551eea3f2e8a9b3b24b3edd2a4de1ac0dc17bc75c9ec07"}, - {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:790a168a808bd00ee42786b8ba883307c0e3684ebb292e0e20009588c426da47"}, - {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4310b5494271e18580d61022c0857eb85d30510d88606fa3b8314790df7f367d"}, - {file = "shapely-2.0.4-cp311-cp311-win32.whl", hash = "sha256:63f3a80daf4f867bd80f5c97fbe03314348ac1b3b70fb1c0ad255a69e3749879"}, - {file = "shapely-2.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:c52ed79f683f721b69a10fb9e3d940a468203f5054927215586c5d49a072de8d"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5bbd974193e2cc274312da16b189b38f5f128410f3377721cadb76b1e8ca5328"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:41388321a73ba1a84edd90d86ecc8bfed55e6a1e51882eafb019f45895ec0f65"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0776c92d584f72f1e584d2e43cfc5542c2f3dd19d53f70df0900fda643f4bae6"}, - {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c75c98380b1ede1cae9a252c6dc247e6279403fae38c77060a5e6186c95073ac"}, - {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e700abf4a37b7b8b90532fa6ed5c38a9bfc777098bc9fbae5ec8e618ac8f30"}, - {file = "shapely-2.0.4-cp312-cp312-win32.whl", hash = "sha256:4f2ab0faf8188b9f99e6a273b24b97662194160cc8ca17cf9d1fb6f18d7fb93f"}, - {file = "shapely-2.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:03152442d311a5e85ac73b39680dd64a9892fa42bb08fd83b3bab4fe6999bfa0"}, - {file = "shapely-2.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:994c244e004bc3cfbea96257b883c90a86e8cbd76e069718eb4c6b222a56f78b"}, - {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05ffd6491e9e8958b742b0e2e7c346635033d0a5f1a0ea083547fcc854e5d5cf"}, - {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbdc1140a7d08faa748256438291394967aa54b40009f54e8d9825e75ef6113"}, - {file = "shapely-2.0.4-cp37-cp37m-win32.whl", hash = "sha256:5af4cd0d8cf2912bd95f33586600cac9c4b7c5053a036422b97cfe4728d2eb53"}, - {file = "shapely-2.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:464157509ce4efa5ff285c646a38b49f8c5ef8d4b340f722685b09bb033c5ccf"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:489c19152ec1f0e5c5e525356bcbf7e532f311bff630c9b6bc2db6f04da6a8b9"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b79bbd648664aa6f44ef018474ff958b6b296fed5c2d42db60078de3cffbc8aa"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:674d7baf0015a6037d5758496d550fc1946f34bfc89c1bf247cabdc415d7747e"}, - {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cd4ccecc5ea5abd06deeaab52fcdba372f649728050c6143cc405ee0c166679"}, - {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5cdcbbe3080181498931b52a91a21a781a35dcb859da741c0345c6402bf00c"}, - {file = "shapely-2.0.4-cp38-cp38-win32.whl", hash = "sha256:55a38dcd1cee2f298d8c2ebc60fc7d39f3b4535684a1e9e2f39a80ae88b0cea7"}, - {file = "shapely-2.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec555c9d0db12d7fd777ba3f8b75044c73e576c720a851667432fabb7057da6c"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9103abd1678cb1b5f7e8e1af565a652e036844166c91ec031eeb25c5ca8af0"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:263bcf0c24d7a57c80991e64ab57cba7a3906e31d2e21b455f493d4aab534aaa"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddf4a9bfaac643e62702ed662afc36f6abed2a88a21270e891038f9a19bc08fc"}, - {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:485246fcdb93336105c29a5cfbff8a226949db37b7473c89caa26c9bae52a242"}, - {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8de4578e838a9409b5b134a18ee820730e507b2d21700c14b71a2b0757396acc"}, - {file = "shapely-2.0.4-cp39-cp39-win32.whl", hash = "sha256:9dab4c98acfb5fb85f5a20548b5c0abe9b163ad3525ee28822ffecb5c40e724c"}, - {file = "shapely-2.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:31c19a668b5a1eadab82ff070b5a260478ac6ddad3a5b62295095174a8d26398"}, - {file = "shapely-2.0.4.tar.gz", hash = "sha256:5dc736127fac70009b8d309a0eeb74f3e08979e530cf7017f2f507ef62e6cfb8"}, -] - -[package.dependencies] -numpy = ">=1.14,<3" - -[package.extras] -docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "simplejson" -version = "3.19.2" -description = "Simple, fast, extensible JSON encoder/decoder for Python" -optional = false -python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "simplejson-3.19.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3471e95110dcaf901db16063b2e40fb394f8a9e99b3fe9ee3acc6f6ef72183a2"}, - {file = "simplejson-3.19.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3194cd0d2c959062b94094c0a9f8780ffd38417a5322450a0db0ca1a23e7fbd2"}, - {file = "simplejson-3.19.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:8a390e56a7963e3946ff2049ee1eb218380e87c8a0e7608f7f8790ba19390867"}, - {file = "simplejson-3.19.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1537b3dd62d8aae644f3518c407aa8469e3fd0f179cdf86c5992792713ed717a"}, - {file = "simplejson-3.19.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a8617625369d2d03766413bff9e64310feafc9fc4f0ad2b902136f1a5cd8c6b0"}, - {file = "simplejson-3.19.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:2c433a412e96afb9a3ce36fa96c8e61a757af53e9c9192c97392f72871e18e69"}, - {file = "simplejson-3.19.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f1c70249b15e4ce1a7d5340c97670a95f305ca79f376887759b43bb33288c973"}, - {file = "simplejson-3.19.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:287e39ba24e141b046812c880f4619d0ca9e617235d74abc27267194fc0c7835"}, - {file = "simplejson-3.19.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6f0a0b41dd05eefab547576bed0cf066595f3b20b083956b1405a6f17d1be6ad"}, - {file = "simplejson-3.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f98d918f7f3aaf4b91f2b08c0c92b1774aea113334f7cde4fe40e777114dbe6"}, - {file = "simplejson-3.19.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d74beca677623481810c7052926365d5f07393c72cbf62d6cce29991b676402"}, - {file = "simplejson-3.19.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f2398361508c560d0bf1773af19e9fe644e218f2a814a02210ac2c97ad70db0"}, - {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ad331349b0b9ca6da86064a3599c425c7a21cd41616e175ddba0866da32df48"}, - {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:332c848f02d71a649272b3f1feccacb7e4f7e6de4a2e6dc70a32645326f3d428"}, - {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25785d038281cd106c0d91a68b9930049b6464288cea59ba95b35ee37c2d23a5"}, - {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18955c1da6fc39d957adfa346f75226246b6569e096ac9e40f67d102278c3bcb"}, - {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:11cc3afd8160d44582543838b7e4f9aa5e97865322844b75d51bf4e0e413bb3e"}, - {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b01fda3e95d07a6148702a641e5e293b6da7863f8bc9b967f62db9461330562c"}, - {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:778331444917108fa8441f59af45886270d33ce8a23bfc4f9b192c0b2ecef1b3"}, - {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9eb117db8d7ed733a7317c4215c35993b815bf6aeab67523f1f11e108c040672"}, - {file = "simplejson-3.19.2-cp310-cp310-win32.whl", hash = "sha256:39b6d79f5cbfa3eb63a869639cfacf7c41d753c64f7801efc72692c1b2637ac7"}, - {file = "simplejson-3.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:5675e9d8eeef0aa06093c1ff898413ade042d73dc920a03e8cea2fb68f62445a"}, - {file = "simplejson-3.19.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed628c1431100b0b65387419551e822987396bee3c088a15d68446d92f554e0c"}, - {file = "simplejson-3.19.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:adcb3332979cbc941b8fff07181f06d2b608625edc0a4d8bc3ffc0be414ad0c4"}, - {file = "simplejson-3.19.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08889f2f597ae965284d7b52a5c3928653a9406d88c93e3161180f0abc2433ba"}, - {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7938a78447174e2616be223f496ddccdbf7854f7bf2ce716dbccd958cc7d13"}, - {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a970a2e6d5281d56cacf3dc82081c95c1f4da5a559e52469287457811db6a79b"}, - {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554313db34d63eac3b3f42986aa9efddd1a481169c12b7be1e7512edebff8eaf"}, - {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d36081c0b1c12ea0ed62c202046dca11438bee48dd5240b7c8de8da62c620e9"}, - {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a3cd18e03b0ee54ea4319cdcce48357719ea487b53f92a469ba8ca8e39df285e"}, - {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66e5dc13bfb17cd6ee764fc96ccafd6e405daa846a42baab81f4c60e15650414"}, - {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:972a7833d4a1fcf7a711c939e315721a88b988553fc770a5b6a5a64bd6ebeba3"}, - {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3e74355cb47e0cd399ead3477e29e2f50e1540952c22fb3504dda0184fc9819f"}, - {file = "simplejson-3.19.2-cp311-cp311-win32.whl", hash = "sha256:1dd4f692304854352c3e396e9b5f0a9c9e666868dd0bdc784e2ac4c93092d87b"}, - {file = "simplejson-3.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:9300aee2a8b5992d0f4293d88deb59c218989833e3396c824b69ba330d04a589"}, - {file = "simplejson-3.19.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b8d940fd28eb34a7084877747a60873956893e377f15a32ad445fe66c972c3b8"}, - {file = "simplejson-3.19.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4969d974d9db826a2c07671273e6b27bc48e940738d768fa8f33b577f0978378"}, - {file = "simplejson-3.19.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c594642d6b13d225e10df5c16ee15b3398e21a35ecd6aee824f107a625690374"}, - {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f5a398b5e77bb01b23d92872255e1bcb3c0c719a3be40b8df146570fe7781a"}, - {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176a1b524a3bd3314ed47029a86d02d5a95cc0bee15bd3063a1e1ec62b947de6"}, - {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3c7363a8cb8c5238878ec96c5eb0fc5ca2cb11fc0c7d2379863d342c6ee367a"}, - {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:346820ae96aa90c7d52653539a57766f10f33dd4be609206c001432b59ddf89f"}, - {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de9a2792612ec6def556d1dc621fd6b2073aff015d64fba9f3e53349ad292734"}, - {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1c768e7584c45094dca4b334af361e43b0aaa4844c04945ac7d43379eeda9bc2"}, - {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:9652e59c022e62a5b58a6f9948b104e5bb96d3b06940c6482588176f40f4914b"}, - {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9c1a4393242e321e344213a90a1e3bf35d2f624aa8b8f6174d43e3c6b0e8f6eb"}, - {file = "simplejson-3.19.2-cp312-cp312-win32.whl", hash = "sha256:7cb98be113911cb0ad09e5523d0e2a926c09a465c9abb0784c9269efe4f95917"}, - {file = "simplejson-3.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:6779105d2fcb7fcf794a6a2a233787f6bbd4731227333a072d8513b252ed374f"}, - {file = "simplejson-3.19.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:061e81ea2d62671fa9dea2c2bfbc1eec2617ae7651e366c7b4a2baf0a8c72cae"}, - {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4280e460e51f86ad76dc456acdbfa9513bdf329556ffc8c49e0200878ca57816"}, - {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11c39fbc4280d7420684494373b7c5904fa72a2b48ef543a56c2d412999c9e5d"}, - {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bccb3e88ec26ffa90f72229f983d3a5d1155e41a1171190fa723d4135523585b"}, - {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb5b50dc6dd671eb46a605a3e2eb98deb4a9af787a08fcdddabe5d824bb9664"}, - {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d94245caa3c61f760c4ce4953cfa76e7739b6f2cbfc94cc46fff6c050c2390c5"}, - {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0e5ffc763678d48ecc8da836f2ae2dd1b6eb2d27a48671066f91694e575173c"}, - {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d222a9ed082cd9f38b58923775152003765016342a12f08f8c123bf893461f28"}, - {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8434dcdd347459f9fd9c526117c01fe7ca7b016b6008dddc3c13471098f4f0dc"}, - {file = "simplejson-3.19.2-cp36-cp36m-win32.whl", hash = "sha256:c9ac1c2678abf9270e7228133e5b77c6c3c930ad33a3c1dfbdd76ff2c33b7b50"}, - {file = "simplejson-3.19.2-cp36-cp36m-win_amd64.whl", hash = "sha256:92c4a4a2b1f4846cd4364855cbac83efc48ff5a7d7c06ba014c792dd96483f6f"}, - {file = "simplejson-3.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0d551dc931638e2102b8549836a1632e6e7cf620af3d093a7456aa642bff601d"}, - {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73a8a4653f2e809049999d63530180d7b5a344b23a793502413ad1ecea9a0290"}, - {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40847f617287a38623507d08cbcb75d51cf9d4f9551dd6321df40215128325a3"}, - {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be893258d5b68dd3a8cba8deb35dc6411db844a9d35268a8d3793b9d9a256f80"}, - {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9eb3cff1b7d71aa50c89a0536f469cb8d6dcdd585d8f14fb8500d822f3bdee4"}, - {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d0f402e787e6e7ee7876c8b05e2fe6464820d9f35ba3f172e95b5f8b699f6c7f"}, - {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fbbcc6b0639aa09b9649f36f1bcb347b19403fe44109948392fbb5ea69e48c3e"}, - {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:2fc697be37585eded0c8581c4788fcfac0e3f84ca635b73a5bf360e28c8ea1a2"}, - {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b0a3eb6dd39cce23801a50c01a0976971498da49bc8a0590ce311492b82c44b"}, - {file = "simplejson-3.19.2-cp37-cp37m-win32.whl", hash = "sha256:49f9da0d6cd17b600a178439d7d2d57c5ef01f816b1e0e875e8e8b3b42db2693"}, - {file = "simplejson-3.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c87c22bd6a987aca976e3d3e23806d17f65426191db36d40da4ae16a6a494cbc"}, - {file = "simplejson-3.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e4c166f743bb42c5fcc60760fb1c3623e8fda94f6619534217b083e08644b46"}, - {file = "simplejson-3.19.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a48679310e1dd5c9f03481799311a65d343748fe86850b7fb41df4e2c00c087"}, - {file = "simplejson-3.19.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0521e0f07cb56415fdb3aae0bbd8701eb31a9dfef47bb57206075a0584ab2a2"}, - {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2d5119b1d7a1ed286b8af37357116072fc96700bce3bec5bb81b2e7057ab41"}, - {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c1467d939932901a97ba4f979e8f2642415fcf02ea12f53a4e3206c9c03bc17"}, - {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49aaf4546f6023c44d7e7136be84a03a4237f0b2b5fb2b17c3e3770a758fc1a0"}, - {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60848ab779195b72382841fc3fa4f71698a98d9589b0a081a9399904487b5832"}, - {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0436a70d8eb42bea4fe1a1c32d371d9bb3b62c637969cb33970ad624d5a3336a"}, - {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:49e0e3faf3070abdf71a5c80a97c1afc059b4f45a5aa62de0c2ca0444b51669b"}, - {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ff836cd4041e16003549449cc0a5e372f6b6f871eb89007ab0ee18fb2800fded"}, - {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3848427b65e31bea2c11f521b6fc7a3145d6e501a1038529da2391aff5970f2f"}, - {file = "simplejson-3.19.2-cp38-cp38-win32.whl", hash = "sha256:3f39bb1f6e620f3e158c8b2eaf1b3e3e54408baca96a02fe891794705e788637"}, - {file = "simplejson-3.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:0405984f3ec1d3f8777c4adc33eac7ab7a3e629f3b1c05fdded63acc7cf01137"}, - {file = "simplejson-3.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:445a96543948c011a3a47c8e0f9d61e9785df2544ea5be5ab3bc2be4bd8a2565"}, - {file = "simplejson-3.19.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a8c3cc4f9dfc33220246760358c8265dad6e1104f25f0077bbca692d616d358"}, - {file = "simplejson-3.19.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af9c7e6669c4d0ad7362f79cb2ab6784d71147503e62b57e3d95c4a0f222c01c"}, - {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:064300a4ea17d1cd9ea1706aa0590dcb3be81112aac30233823ee494f02cb78a"}, - {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9453419ea2ab9b21d925d0fd7e3a132a178a191881fab4169b6f96e118cc25bb"}, - {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e038c615b3906df4c3be8db16b3e24821d26c55177638ea47b3f8f73615111c"}, - {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16ca9c90da4b1f50f089e14485db8c20cbfff2d55424062791a7392b5a9b3ff9"}, - {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1018bd0d70ce85f165185d2227c71e3b1e446186f9fa9f971b69eee223e1e3cd"}, - {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e8dd53a8706b15bc0e34f00e6150fbefb35d2fd9235d095b4f83b3c5ed4fa11d"}, - {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2d022b14d7758bfb98405672953fe5c202ea8a9ccf9f6713c5bd0718eba286fd"}, - {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:febffa5b1eda6622d44b245b0685aff6fb555ce0ed734e2d7b1c3acd018a2cff"}, - {file = "simplejson-3.19.2-cp39-cp39-win32.whl", hash = "sha256:4edcd0bf70087b244ba77038db23cd98a1ace2f91b4a3ecef22036314d77ac23"}, - {file = "simplejson-3.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:aad7405c033d32c751d98d3a65801e2797ae77fac284a539f6c3a3e13005edc4"}, - {file = "simplejson-3.19.2-py3-none-any.whl", hash = "sha256:bcedf4cae0d47839fee7de344f96b5694ca53c786f28b5f773d4f0b265a159eb"}, - {file = "simplejson-3.19.2.tar.gz", hash = "sha256:9eb442a2442ce417801c912df68e1f6ccfcd41577ae7274953ab3ad24ef7d82c"}, -] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -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 = "sortedcontainers" -version = "2.4.0" -description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -optional = false -python-versions = "*" -files = [ - {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, - {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.30" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"}, - {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"}, - {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false -python-versions = "*" -files = [ - {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, - {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - -[[package]] -name = "tblib" -version = "3.0.0" -description = "Traceback serialization library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"}, - {file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"}, -] - -[[package]] -name = "text-unidecode" -version = "1.3" -description = "The most basic Text::Unidecode port" -optional = false -python-versions = "*" -files = [ - {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, - {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, -] - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tomlkit" -version = "0.12.5" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, - {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, -] - -[[package]] -name = "toolz" -version = "0.12.1" -description = "List processing tools and functional utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, - {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, -] - -[[package]] -name = "tornado" -version = "6.4" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = false -python-versions = ">= 3.8" -files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, -] - -[[package]] -name = "traitlets" -version = "5.14.3" -description = "Traitlets Python configuration system" -optional = false -python-versions = ">=3.8" -files = [ - {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, - {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] - -[[package]] -name = "typing-extensions" -version = "4.12.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, - {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, -] - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - -[[package]] -name = "urllib3" -version = "2.2.1" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, -] - -[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 = "virtualenv" -version = "20.26.2" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, - {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "zict" -version = "3.0.0" -description = "Mutable mapping tools" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zict-3.0.0-py2.py3-none-any.whl", hash = "sha256:5796e36bd0e0cc8cf0fbc1ace6a68912611c1dbd74750a3f3026b9b9d6a327ae"}, - {file = "zict-3.0.0.tar.gz", hash = "sha256:e321e263b6a97aafc0790c3cfb3c04656b7066e6738c37fffcca95d803c9fba5"}, -] - -[[package]] -name = "zipp" -version = "3.19.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.19.0-py3-none-any.whl", hash = "sha256:96dc6ad62f1441bcaccef23b274ec471518daf4fbbc580341204936a5a3dddec"}, - {file = "zipp-3.19.0.tar.gz", hash = "sha256:952df858fb3164426c976d9338d3961e8e8b3758e2e059e0f754b8c4262625ee"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - -[metadata] -lock-version = "2.0" -python-versions = "3.10.12" -content-hash = "b1eeebe27531b0d60844c12edd9d5e46a9d5f55ddbb2f710b5b2d9e2210787e1" diff --git a/datascience/pyproject.toml b/datascience/pyproject.toml deleted file mode 100644 index 73cea96303..0000000000 --- a/datascience/pyproject.toml +++ /dev/null @@ -1,84 +0,0 @@ -[tool.poetry] -name = "src" -version = "0.1.0" -description = "" -authors = ["Thomas Brosset "] - -[tool.poetry.dependencies] -python = "3.10.12" -python-dotenv = "^1.0.0" -pandas = "^2.0.2" -GeoAlchemy2 = "^0.13.3" -cx-Oracle = "^8.3.0" -simplejson = "^3.19.1" -pytz = "^2023.3" -prefect = "^1.4" -coverage = "^7.2.7" -pytest = "^7.3.1" -geopandas = "^0.13.2" -SQLAlchemy = "^2.0.16" -psycopg2-binary = "^2.9.6" -css-inline = "^0.13.0" -lxml = "^6.0.2" - -[tool.poetry.group.dev.dependencies] -# Linting -## Type Checking and Data Validation -mypy = "^1.3.0" -## Code formatting -black = "^24.3.0" -## Code quality -isort = "^5.12.0" -pylint = "^2.17.4" -## Automation and management -pre-commit = "^3.3.3" -ipython = "^8.26.0" - -[tool.poetry.scripts] -cli = "bin.cli:cli" - -################################################################################# -# Tooling configs # -################################################################################# -[tool.black] -line-length = 79 - -[tool.coverage.run] -source = ["."] -omit = ['src/utils/', 'tests/'] - -[tool.isort] -profile = "black" -atomic = "true" -combine_as_imports = "true" -line_length = 79 - -[tool.pylint.basic] -good-names-rgxs = ["^Test_.*$", "logger"] - -[tool.pylint.messages_control] -disable = [ - # Explicitly document only as needed - "missing-module-docstring", - "missing-class-docstring", - "missing-function-docstring", - # Black & Flake8 purview - "line-too-long", - "bad-continuation", - "c-extension-no-member", - # Ignore errors resulting from Jupyter notebook-style programming - "invalid-name", - "redefined-outer-name", - "reimported", - "ungrouped-imports", - "wrong-import-order", - "wrong-import-position", -] - -[tool.pylint.similarities] -# Ignore imports when computing similarities. -ignore-imports = "yes" - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/datascience/requirements-dev.txt b/datascience/requirements-dev.txt deleted file mode 100644 index b7b7a3e74f..0000000000 --- a/datascience/requirements-dev.txt +++ /dev/null @@ -1,87 +0,0 @@ -astroid==2.15.8 ; python_full_version == "3.10.12" -attrs==23.2.0 ; python_full_version == "3.10.12" -black==24.4.2 ; python_full_version == "3.10.12" -certifi==2024.2.2 ; python_full_version == "3.10.12" -cfgv==3.4.0 ; python_full_version == "3.10.12" -charset-normalizer==3.3.2 ; python_full_version == "3.10.12" -click-plugins==1.1.1 ; python_full_version == "3.10.12" -click==8.1.7 ; python_full_version == "3.10.12" -cligj==0.7.2 ; python_full_version == "3.10.12" -cloudpickle==3.0.0 ; python_full_version == "3.10.12" -colorama==0.4.6 ; python_full_version == "3.10.12" and (sys_platform == "win32" or platform_system == "Windows") -coverage==7.5.3 ; python_full_version == "3.10.12" -croniter==2.0.5 ; python_full_version == "3.10.12" -css-inline==0.13.0 ; python_full_version == "3.10.12" -cx-oracle==8.3.0 ; python_full_version == "3.10.12" -dask==2024.5.1 ; python_full_version == "3.10.12" -dill==0.3.8 ; python_full_version == "3.10.12" -distlib==0.3.8 ; python_full_version == "3.10.12" -distributed==2024.5.1 ; python_full_version == "3.10.12" -docker==7.1.0 ; python_full_version == "3.10.12" -exceptiongroup==1.2.1 ; python_full_version == "3.10.12" -filelock==3.14.0 ; python_full_version == "3.10.12" -fiona==1.9.6 ; python_full_version == "3.10.12" -fsspec==2024.5.0 ; python_full_version == "3.10.12" -geoalchemy2==0.13.3 ; python_full_version == "3.10.12" -geopandas==0.13.2 ; python_full_version == "3.10.12" -greenlet==3.0.3 ; (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_full_version == "3.10.12" -identify==2.5.36 ; python_full_version == "3.10.12" -idna==3.7 ; python_full_version == "3.10.12" -importlib-metadata==7.1.0 ; python_full_version == "3.10.12" -importlib-resources==6.4.0 ; python_full_version == "3.10.12" -iniconfig==2.0.0 ; python_full_version == "3.10.12" -isort==5.13.2 ; python_full_version == "3.10.12" -jinja2==3.1.4 ; python_full_version == "3.10.12" -lazy-object-proxy==1.10.0 ; python_full_version == "3.10.12" -locket==1.0.0 ; python_full_version == "3.10.12" -markupsafe==2.1.5 ; python_full_version == "3.10.12" -marshmallow-oneofschema==3.1.1 ; python_full_version == "3.10.12" -marshmallow==3.21.2 ; python_full_version == "3.10.12" -mccabe==0.7.0 ; python_full_version == "3.10.12" -msgpack==1.0.8 ; python_full_version == "3.10.12" -mypy-extensions==1.0.0 ; python_full_version == "3.10.12" -mypy==1.10.0 ; python_full_version == "3.10.12" -nodeenv==1.9.0 ; python_full_version == "3.10.12" -numpy==1.26.4 ; python_full_version == "3.10.12" -packaging==24.0 ; python_full_version == "3.10.12" -pandas==2.2.2 ; python_full_version == "3.10.12" -partd==1.4.2 ; python_full_version == "3.10.12" -pathspec==0.12.1 ; python_full_version == "3.10.12" -pendulum==3.0.0 ; python_full_version == "3.10.12" -platformdirs==4.2.2 ; python_full_version == "3.10.12" -pluggy==1.5.0 ; python_full_version == "3.10.12" -pre-commit==3.7.1 ; python_full_version == "3.10.12" -prefect==1.4.1 ; python_full_version == "3.10.12" -psutil==5.9.8 ; python_full_version == "3.10.12" -psycopg2-binary==2.9.9 ; python_full_version == "3.10.12" -pylint==2.17.7 ; python_full_version == "3.10.12" -pyproj==3.6.1 ; python_full_version == "3.10.12" -pytest==7.4.4 ; python_full_version == "3.10.12" -python-box==7.1.1 ; python_full_version == "3.10.12" -python-dateutil==2.9.0.post0 ; python_full_version == "3.10.12" -python-dotenv==1.0.1 ; python_full_version == "3.10.12" -python-slugify==8.0.4 ; python_full_version == "3.10.12" -pytz==2023.4 ; python_full_version == "3.10.12" -pywin32==306 ; sys_platform == "win32" and python_full_version == "3.10.12" -pyyaml==6.0.1 ; python_full_version == "3.10.12" -requests==2.32.3 ; python_full_version == "3.10.12" -shapely==2.0.4 ; python_full_version == "3.10.12" -simplejson==3.19.2 ; python_full_version == "3.10.12" -six==1.16.0 ; python_full_version == "3.10.12" -sortedcontainers==2.4.0 ; python_full_version == "3.10.12" -sqlalchemy==2.0.30 ; python_full_version == "3.10.12" -tabulate==0.9.0 ; python_full_version == "3.10.12" -tblib==3.0.0 ; python_full_version == "3.10.12" -text-unidecode==1.3 ; python_full_version == "3.10.12" -toml==0.10.2 ; python_full_version == "3.10.12" -tomli==2.0.1 ; python_full_version == "3.10.12" -tomlkit==0.12.5 ; python_full_version == "3.10.12" -toolz==0.12.1 ; python_full_version == "3.10.12" -tornado==6.4 ; python_full_version == "3.10.12" -typing-extensions==4.12.0 ; python_full_version == "3.10.12" -tzdata==2024.1 ; python_full_version == "3.10.12" -urllib3==2.2.1 ; python_full_version == "3.10.12" -virtualenv==20.26.2 ; python_full_version == "3.10.12" -wrapt==1.16.0 ; python_full_version == "3.10.12" -zict==3.0.0 ; python_full_version == "3.10.12" -zipp==3.19.0 ; python_full_version == "3.10.12" diff --git a/datascience/requirements.txt b/datascience/requirements.txt deleted file mode 100644 index 1075f74521..0000000000 --- a/datascience/requirements.txt +++ /dev/null @@ -1,68 +0,0 @@ -attrs==23.2.0 ; python_full_version == "3.10.12" -certifi==2024.2.2 ; python_full_version == "3.10.12" -charset-normalizer==3.3.2 ; python_full_version == "3.10.12" -click-plugins==1.1.1 ; python_full_version == "3.10.12" -click==8.1.7 ; python_full_version == "3.10.12" -cligj==0.7.2 ; python_full_version == "3.10.12" -cloudpickle==3.0.0 ; python_full_version == "3.10.12" -colorama==0.4.6 ; python_full_version == "3.10.12" and (sys_platform == "win32" or platform_system == "Windows") -coverage==7.5.3 ; python_full_version == "3.10.12" -croniter==2.0.5 ; python_full_version == "3.10.12" -css-inline==0.13.0 ; python_full_version == "3.10.12" -cx-oracle==8.3.0 ; python_full_version == "3.10.12" -dask==2024.5.1 ; python_full_version == "3.10.12" -distributed==2024.5.1 ; python_full_version == "3.10.12" -docker==7.1.0 ; python_full_version == "3.10.12" -exceptiongroup==1.2.1 ; python_full_version == "3.10.12" -fiona==1.9.6 ; python_full_version == "3.10.12" -fsspec==2024.5.0 ; python_full_version == "3.10.12" -geoalchemy2==0.13.3 ; python_full_version == "3.10.12" -geopandas==0.13.2 ; python_full_version == "3.10.12" -greenlet==3.0.3 ; (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_full_version == "3.10.12" -idna==3.7 ; python_full_version == "3.10.12" -importlib-metadata==7.1.0 ; python_full_version == "3.10.12" -importlib-resources==6.4.0 ; python_full_version == "3.10.12" -iniconfig==2.0.0 ; python_full_version == "3.10.12" -jinja2==3.1.4 ; python_full_version == "3.10.12" -locket==1.0.0 ; python_full_version == "3.10.12" -markupsafe==2.1.5 ; python_full_version == "3.10.12" -marshmallow-oneofschema==3.1.1 ; python_full_version == "3.10.12" -marshmallow==3.21.2 ; python_full_version == "3.10.12" -msgpack==1.0.8 ; python_full_version == "3.10.12" -mypy-extensions==1.0.0 ; python_full_version == "3.10.12" -numpy==1.26.4 ; python_full_version == "3.10.12" -packaging==24.0 ; python_full_version == "3.10.12" -pandas==2.2.2 ; python_full_version == "3.10.12" -partd==1.4.2 ; python_full_version == "3.10.12" -pendulum==3.0.0 ; python_full_version == "3.10.12" -pluggy==1.5.0 ; python_full_version == "3.10.12" -prefect==1.4.1 ; python_full_version == "3.10.12" -psutil==5.9.8 ; python_full_version == "3.10.12" -psycopg2-binary==2.9.9 ; python_full_version == "3.10.12" -pyproj==3.6.1 ; python_full_version == "3.10.12" -pytest==7.4.4 ; python_full_version == "3.10.12" -python-box==7.1.1 ; python_full_version == "3.10.12" -python-dateutil==2.9.0.post0 ; python_full_version == "3.10.12" -python-dotenv==1.0.1 ; python_full_version == "3.10.12" -python-slugify==8.0.4 ; python_full_version == "3.10.12" -pytz==2023.4 ; python_full_version == "3.10.12" -pywin32==306 ; sys_platform == "win32" and python_full_version == "3.10.12" -pyyaml==6.0.1 ; python_full_version == "3.10.12" -requests==2.32.3 ; python_full_version == "3.10.12" -shapely==2.0.4 ; python_full_version == "3.10.12" -simplejson==3.19.2 ; python_full_version == "3.10.12" -six==1.16.0 ; python_full_version == "3.10.12" -sortedcontainers==2.4.0 ; python_full_version == "3.10.12" -sqlalchemy==2.0.30 ; python_full_version == "3.10.12" -tabulate==0.9.0 ; python_full_version == "3.10.12" -tblib==3.0.0 ; python_full_version == "3.10.12" -text-unidecode==1.3 ; python_full_version == "3.10.12" -toml==0.10.2 ; python_full_version == "3.10.12" -tomli==2.0.1 ; python_full_version == "3.10.12" -toolz==0.12.1 ; python_full_version == "3.10.12" -tornado==6.4 ; python_full_version == "3.10.12" -typing-extensions==4.12.0 ; python_full_version == "3.10.12" -tzdata==2024.1 ; python_full_version == "3.10.12" -urllib3==2.2.1 ; python_full_version == "3.10.12" -zict==3.0.0 ; python_full_version == "3.10.12" -zipp==3.19.0 ; python_full_version == "3.10.12" diff --git a/datascience/scripts/run_flows.py b/datascience/scripts/run_flows.py deleted file mode 100644 index b6d61676b2..0000000000 --- a/datascience/scripts/run_flows.py +++ /dev/null @@ -1,9 +0,0 @@ -from src.pipeline.flows.admin_areas import flow as flow_admin_areas -from src.pipeline.flows.facade_areas import flow as flow_facade_areas -from src.pipeline.flows.fao_areas import flow as flow_fao_areas -from src.pipeline.flows.regulations import flow as flow_regulations - -flow_regulations.run() -flow_facade_areas.run() -flow_fao_areas.run() -flow_admin_areas.run() diff --git a/datascience/setup.py b/datascience/setup.py deleted file mode 100644 index 65c700acee..0000000000 --- a/datascience/setup.py +++ /dev/null @@ -1,12 +0,0 @@ -from setuptools import find_packages, setup - -with open("requirements.txt") as f: - requirements = f.read().splitlines() - -setup( - name="src", - version="1.0", - package_dir={"": "."}, - packages=find_packages(exclude=["tests", "tests.*"]), - install_requires=requirements, -) diff --git a/datascience/src/__init__.py b/datascience/src/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/db_config.py b/datascience/src/db_config.py deleted file mode 100644 index dc5071ffc1..0000000000 --- a/datascience/src/db_config.py +++ /dev/null @@ -1,91 +0,0 @@ -import os - -import sqlalchemy as sa -from dotenv import load_dotenv - -from config import ROOT_DIRECTORY - -load_dotenv(ROOT_DIRECTORY / ".env") - -db_env = { - "fmc": { - "client": "ORACLE_CLIENT", - "host": "ORACLE_HOST", - "port": "ORACLE_PORT", - "sid": "ORACLE_FMC_SID", - "usr": "ORACLE_FMC_USER", - "pwd": "ORACLE_FMC_PASSWORD", - }, - "monitorenv_remote": { - "client": "MONITORENV_REMOTE_DB_CLIENT", - "host": "MONITORENV_REMOTE_DB_HOST", - "port": "MONITORENV_REMOTE_DB_PORT", - "sid": "MONITORENV_REMOTE_DB_NAME", - "usr": "MONITORENV_REMOTE_DB_USER", - "pwd": "MONITORENV_REMOTE_DB_PWD", - }, - "monitorfish_local": { - "client": "MONITORFISH_LOCAL_CLIENT", - "host": "MONITORFISH_LOCAL_HOST", - "port": "MONITORFISH_LOCAL_PORT", - "sid": "MONITORFISH_LOCAL_NAME", - "usr": "MONITORFISH_LOCAL_USER", - "pwd": "MONITORFISH_LOCAL_PWD", - }, - "cacem_local": { - "client": "CACEM_LOCAL_CLIENT", - "host": "CACEM_LOCAL_HOST", - "port": "CACEM_LOCAL_PORT", - "sid": "CACEM_LOCAL_NAME", - "usr": "CACEM_LOCAL_USER", - "pwd": "CACEM_LOCAL_PWD", - }, -} - - -def make_connection_string(db: str) -> str: - """Returns the connection string for the designated database. - - Args: - db (str): Database name. Possible values : - 'ocan', 'fmc', 'monitorenv_remote', 'monistorfish_local' - - Returns: - str: connection string for selected database. - - Raises: - ValueError: with credentials for the selected database are not found in - environment variables. - """ - - try: - CLIENT = os.environ[db_env[db]["client"]] - HOST = os.environ[db_env[db]["host"]] - PORT = os.environ[db_env[db]["port"]] - SID = os.environ[db_env[db]["sid"]] - USER = os.environ[db_env[db]["usr"]] - PWD = os.environ[db_env[db]["pwd"]] - except KeyError as e: - raise KeyError( - "Database connection credentials not found in environment: ", - e.args, - ) - - return f"{CLIENT}://{USER}:{PWD}@{HOST}:{PORT}/{SID}" - - -def create_engine(db: str, **kwargs) -> sa.engine.Engine: - """Returns sqlalchemy engine for designated database. - - Args: - db (str): Database name. Possible values : - 'ocan', 'fmc', 'monitorenv_remote', 'monistorfish_local', 'cacem_local' - - Returns: - sa.engine.Engine: sqlalchemy engine for selected database. - """ - connection_string = make_connection_string(db) - - engine = sa.create_engine(connection_string, **kwargs) - - return engine diff --git a/datascience/src/pipeline/__init__.py b/datascience/src/pipeline/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/pipeline/data/.gitkeep b/datascience/src/pipeline/data/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/pipeline/data/dummy_control_objectives.csv b/datascience/src/pipeline/data/dummy_control_objectives.csv deleted file mode 100644 index c0a66810ca..0000000000 --- a/datascience/src/pipeline/data/dummy_control_objectives.csv +++ /dev/null @@ -1,17 +0,0 @@ -year,facade,theme_level_1,theme_level_2,target_number_of_controls -2023,NAMO,Police des mouillages,,1 -2023,SA,Police des mouillages,,2 -2023,MED,Police des mouillages,,3 -2023,Martinique,Police des mouillages,,4 -2023,MEMN,Rejets illicites,Carénage sauvage,5 -2023,SA,Rejets illicites,Carénage sauvage,6 -2023,MEMN,Police des espèces protégées et de leurs habitats (faune et flore),Perturbation d'animaux,7 -2023,NAMO,Police des espèces protégées et de leurs habitats (faune et flore),Perturbation d'animaux,8 -2023,MED,Police des espèces protégées et de leurs habitats (faune et flore),Perturbation d'animaux,9 -2023,Sud Océan Indien,Police des espèces protégées et de leurs habitats (faune et flore),Perturbation d'animaux,10 -2023,Martinique,Police des espèces protégées et de leurs habitats (faune et flore),Perturbation d'animaux,11 -2023,MEMN,Travaux en milieu marin,,12 -2023,NAMO,Travaux en milieu marin,,13 -2023,SA,Travaux en milieu marin,,14 -2023,MED,Travaux en milieu marin,,15 -2023,Sud Océan Indien,Travaux en milieu marin,,16 \ No newline at end of file diff --git a/datascience/src/pipeline/emails/stylesheets/email_to_control_units.css b/datascience/src/pipeline/emails/stylesheets/email_to_control_units.css deleted file mode 100644 index cfa6a171e0..0000000000 --- a/datascience/src/pipeline/emails/stylesheets/email_to_control_units.css +++ /dev/null @@ -1,11 +0,0 @@ -h1 { - font-size: 1.999rem; -} - -h2 { -font-size: 1.414rem; -} - -h3 { - font-size: 1.2rem; -} \ No newline at end of file diff --git a/datascience/src/pipeline/emails/stylesheets/splendid.css b/datascience/src/pipeline/emails/stylesheets/splendid.css deleted file mode 100644 index 9ac0caff06..0000000000 --- a/datascience/src/pipeline/emails/stylesheets/splendid.css +++ /dev/null @@ -1,128 +0,0 @@ -html { - font-size: 16px; -} - -body { - line-height: 1.85; - color: #444; - font-family: 'Open Sans', Helvetica, sans-serif; - font-weight: 300; - margin: 6rem auto 1rem; - max-width: 54rem; - /* text-align: center; */ -} - -p { - color: #777; - font-size: 1rem; - margin-bottom: 1.3rem; -} - -h1, -h2, -h3, -h4 { - margin: 1.414rem 0 .5rem; - font-weight: inherit; - line-height: 1.42; -} - -h1 { - margin-top: 0; - font-size: 3.998rem; -} - -h2 { - font-size: 2.827rem; -} - -h3 { - font-size: 1.999rem; -} - -h4 { - font-size: 1.414rem; -} - -h5 { - font-size: 1.121rem; -} - -h6 { - font-size: .88rem; -} - -strong { - font-weight: bold; -} - -a, -a:visited { - color: #3498db; -} - -a:hover, -a:focus, -a:active { - color: #2980b9; -} - -pre { - background-color: #fafafa; - padding: 1rem; - text-align: left; -} - -blockquote { - margin: 0; - border-left: 5px solid #7a7a7a; - font-style: italic; - padding: 1.33em; - text-align: left; -} - -ul, -ol, -li { - text-align: left; -} - -table { - border-collapse: collapse; - margin: 25px 0; - font-size: 1em; - min-width: 400px; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); - margin-left: auto; - margin-right: auto; -} - -thead tr { - background-color: rgb(59, 69, 89); - color: #ffffff; -} - -th, td { - padding: 12px 15px; -} - -tbody tr { - border-bottom: 1px solid #dddddd; -} - -tbody tr:nth-of-type(even) { - background-color: #f3f3f3; -} - -tbody tr:last-of-type { - border-bottom: 2px solid rgb(59, 69, 89); -} - -tbody tr:hover { - background-color: #e2e2e2; -} - -.no-vertical-margin, .no-vertical-margin * { - margin-top: 0; - margin-bottom: 0; -} \ No newline at end of file diff --git a/datascience/src/pipeline/emails/templates/email_actions_to_units.jinja b/datascience/src/pipeline/emails/templates/email_actions_to_units.jinja deleted file mode 100644 index e4cd89a3f4..0000000000 --- a/datascience/src/pipeline/emails/templates/email_actions_to_units.jinja +++ /dev/null @@ -1,58 +0,0 @@ - - - - Bilan hebdomadaire contrôle de l'environnement marin - - - - -
-

Bilan hebdomadaire contrôle de l'environnement marin

-
-
-
-
-

Bonjour,

-

Vous trouverez ci-dessous les données des contrôles et des surveillances de l'environnement marin effectués par votre unité - ({{ control_unit_name }}) entre le {{ from_date }} et le {{ to_date }} que vous avez rapportés au Centre d'Appui au Contrôle de l'Environnement Marin (CACEM), - ainsi que les éventuels contrôles et surveillances de l'environnement marin réalisés antérieurement et ayant été rapportés ou mis à jour pendant cette période.

-

Seuls les contrôles et surveillances dont les données sont complètes sont transmis dans ce bilan hebdomadaire. Si certaines données n'ont pas encore été transmises (par ex. l'établissement d'un PV ou non), - il est normal que le contrôle ne figure pas encore dans le rapport.

-

Si des données sont manquantes, incorrectes ou incomplètes, ou pour toute remarque concernant ce bilan, n'hésitez pas à contacter le CACEM : {{ cacem_email_address }}.

-
-
-
-

Contrôles et surveillances réalisés la semaine passée

-

Contrôles

- {{ controls }} - -

Surveillances

- {{ surveillances }} -
-
-
-

Contrôles et surveillances antérieurs rapportés ou mis à jour la semaine passée

-

Contrôles

- {{ late_controls }} - -

Surveillances

- {{ late_surveillances }} -
-
-
-

- Pour réaliser vos bilans, ces données sont stockées, mises en forme en tableaux de bord et accessibles en temps réel sur - l'interface Metabase : pour obtenir vos accès nominatifs, veuillez vous rapprocher de - {{ cacem_analyst_name }} ({{ cacem_analyst_email }}), analyste de données pour le CACEM et administrateur Metabase. -

-
-
-
-
-

Centre d'Appui au Contrôle de l'Environnement Marin - Tél : +33 2 90 74 32 55

-
- - \ No newline at end of file diff --git a/datascience/src/pipeline/entities/__init__.py b/datascience/src/pipeline/entities/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/pipeline/entities/actions_emailing.py b/datascience/src/pipeline/entities/actions_emailing.py deleted file mode 100644 index a44c40d6ba..0000000000 --- a/datascience/src/pipeline/entities/actions_emailing.py +++ /dev/null @@ -1,48 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime -from enum import Enum -from typing import List - -import pandas as pd - -from src.pipeline.helpers.dates import Period - - -class EnvActionType(Enum): - CONTROL = "CONTROL" - SURVEILLANCE = "SURVEILLANCE" - - -@dataclass -class ControlUnit: - control_unit_id: int - control_unit_name: str - email_addresses: List[str] - - -@dataclass -class ControlUnitActions: - """ - Control unit and its environment control actions between two dates. - """ - - control_unit: ControlUnit - period: Period - controls: pd.DataFrame - late_controls: pd.DataFrame - surveillances: pd.DataFrame - late_surveillances: pd.DataFrame - - -@dataclass -class ControlUnitActionsSentMessage: - control_unit_id: int - control_unit_name: str - email_address: str - sending_datetime_utc: datetime - actions_min_datetime_utc: datetime - actions_max_datetime_utc: datetime - number_of_actions: int - success: bool - error_code: int - error_message: str diff --git a/datascience/src/pipeline/flows/__init__.py b/datascience/src/pipeline/flows/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/pipeline/flows/admin_areas.py b/datascience/src/pipeline/flows/admin_areas.py deleted file mode 100644 index 6b14446bb8..0000000000 --- a/datascience/src/pipeline/flows/admin_areas.py +++ /dev/null @@ -1,239 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, task - -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_3_miles_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/3_miles_areas.sql") - - -@task(checkpoint=False) -def load_3_miles_areas( - three_miles_areas: pd.DataFrame, -): - load( - three_miles_areas, - table_name="3_miles_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_6_miles_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/6_miles_areas.sql") - - -@task(checkpoint=False) -def load_6_miles_areas( - six_miles_areas: pd.DataFrame, -): - load( - six_miles_areas, - table_name="6_miles_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_12_miles_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/12_miles_areas.sql") - - -@task(checkpoint=False) -def load_12_miles_areas( - twelve_miles_areas: pd.DataFrame, -): - load( - twelve_miles_areas, - table_name="12_miles_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_eez_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/eez_areas.sql") - - -@task(checkpoint=False) -def load_eez_areas( - eez_areas: pd.DataFrame, -): - load( - eez_areas, - table_name="eez_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_aem_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/aem_areas.sql") - - -@task(checkpoint=False) -def load_aem_areas(aem_areas: pd.DataFrame): - load( - aem_areas, - table_name="aem_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_departments_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/departments_areas.sql") - - -@task(checkpoint=False) -def load_departments_areas(departments_areas: pd.DataFrame): - load( - departments_areas, - table_name="departments_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_saltwater_limit_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/saltwater_limit_areas.sql") - - -@task(checkpoint=False) -def load_saltwater_limit_areas(saltwater_limit_areas: pd.DataFrame): - load( - saltwater_limit_areas, - table_name="saltwater_limit_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_transversal_sea_limit_areas() -> pd.DataFrame: - return extract("monitorfish_local", "cross/cnsp/transversal_sea_limit_areas.sql") - - -@task(checkpoint=False) -def load_transversal_sea_limit_areas(transversal_sea_limit_areas: pd.DataFrame): - load( - transversal_sea_limit_areas, - table_name="transversal_sea_limit_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - -@task(checkpoint=False) -def extract_territorial_seas() -> pd.DataFrame: - return extract("cacem_local", "cross/cacem/territorial_seas.sql") - - -@task(checkpoint=False) -def load_territorial_seas(territorial_seas: pd.DataFrame): - load( - territorial_seas, - table_name="territorial_seas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -@task(checkpoint=False) -def extract_straight_baseline() -> pd.DataFrame: - return extract("cacem_local", "cross/cacem/straight_baseline.sql") - - -@task(checkpoint=False) -def load_straight_baseline(straight_baseline: pd.DataFrame): - load( - straight_baseline, - table_name="straight_baseline", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - -@task(checkpoint=False) -def extract_low_water_line() -> pd.DataFrame: - return extract("cacem_local", "cross/cacem/low_water_line.sql") - - -@task(checkpoint=False) -def load_low_water_line(low_water_line: pd.DataFrame): - load( - low_water_line, - table_name="low_water_line", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -with Flow("Administrative areas") as flow: - - three_miles_areas = extract_3_miles_areas() - load_3_miles_areas(three_miles_areas) - - six_miles_areas = extract_6_miles_areas() - load_6_miles_areas(six_miles_areas) - - twelve_miles_areas = extract_12_miles_areas() - load_12_miles_areas(twelve_miles_areas) - - eez_areas = extract_eez_areas() - load_eez_areas(eez_areas) - - aem_areas = extract_aem_areas() - load_aem_areas(aem_areas) - - departments_areas = extract_departments_areas() - load_departments_areas(departments_areas) - - saltwater_limit_areas = extract_saltwater_limit_areas() - load_saltwater_limit_areas(saltwater_limit_areas) - - transversal_sea_limit_areas = extract_transversal_sea_limit_areas() - load_transversal_sea_limit_areas(transversal_sea_limit_areas) - - territorial_seas = extract_territorial_seas() - load_territorial_seas(territorial_seas) - - straight_baseline = extract_straight_baseline() - load_straight_baseline(straight_baseline) - - low_water_line = extract_low_water_line() - load_low_water_line(low_water_line) - - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/amp_cacem.py b/datascience/src/pipeline/flows/amp_cacem.py deleted file mode 100644 index 2fd97f5b2f..0000000000 --- a/datascience/src/pipeline/flows/amp_cacem.py +++ /dev/null @@ -1,174 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, case, task -from sqlalchemy import text - -from src.db_config import create_engine -from src.pipeline.generic_tasks import delete_rows, extract, load -from src.pipeline.utils import psql_insert_copy -from src.pipeline.shared_tasks.update_queries import delete_required, insert_required, merge_hashes, select_ids_to_delete, select_ids_to_insert, select_ids_to_update, update_required - - - -@task(checkpoint=False) -def extract_local_hashes() -> pd.DataFrame: - """ - Extract amp hashes from cacem - - Returns: - pd.DataFrame: GeoDataFrame of amp ids + row_hash - """ - return extract( - db_name="cacem_local", query_filepath="cross/cacem/amp_hashes.sql" - ) - - -@task(checkpoint=False) -def extract_remote_hashes() -> pd.DataFrame: - """ - Extract amp hashes from monitorenv - - Returns: - pd.DataFrame: GeoDataFrame of amp ids + row_hash - """ - return extract( - db_name="monitorenv_remote", - query_filepath="monitorenv/amp_hashes.sql", - ) - - - -@task(checkpoint=False) -def delete(ids_to_delete: set): - logger = prefect.context.get("logger") - delete_rows( - table_name="amp_cacem", - schema="public", - db_name="monitorenv_remote", - table_id_column="id", - ids_to_delete=ids_to_delete, - logger=logger, - ) - - -@task(checkpoint=False) -def extract_new_amp(ids_to_upsert: set) -> pd.DataFrame: - return extract( - "cacem_local", - "cross/cacem/amp.sql", - params={"ids": tuple(ids_to_upsert)}, - ) - - - -@task(checkpoint=False) -def update_amps(new_amps: pd.DataFrame): - """Load the output of ``extract_rows_to_update`` task into ``amp`` - table. - - Args: - new_regulations (pd.DataFrame): output of ``extract_rows_to_update`` task. - """ - e = create_engine("monitorenv_remote") - logger = prefect.context.get("logger") - - with e.begin() as connection: - logger.info("Creating temporary table") - connection.execute( - text( - """CREATE TEMP TABLE tmp_amp_cacem( - id serial, - geom public.geometry(MultiPolygon,4326), - mpa_oriname text, - des_desigfr text, - row_hash text, - mpa_type text, - ref_reg text, - url_legicem text) - ON COMMIT DROP;""" - ) - ) - - columns_to_load = [ - "id", - "geom", - "mpa_oriname", - "des_desigfr", - "row_hash", - "mpa_type", - "ref_reg", - "url_legicem" - ] - - logger.info("Loading to temporary table") - - new_amps[columns_to_load].to_sql( - "tmp_amp_cacem", - connection, - if_exists="append", - index=False, - method=psql_insert_copy, - ) - - logger.info(f"Updating amp_cacem from temporary table {len(new_amps)}") - connection.execute( - text( - """UPDATE amp_cacem amp - SET geom = tmp.geom, - mpa_oriname = tmp.mpa_oriname, - des_desigfr = tmp.des_desigfr, - row_hash = tmp.row_hash, - mpa_type = tmp.mpa_type, - ref_reg = tmp.ref_reg, - url_legicem = tmp.url_legicem - FROM tmp_amp_cacem tmp - where amp.id = tmp.id; - """ - ) - ) - -@task(checkpoint=False) -def load_new_amps(new_amp: pd.DataFrame): - """Load the output of ``extract_rows_to_upsert`` task into ``amp`` - table. - - Args: - new_amp (pd.DataFrame): output of ``extract_rows_to_upsert`` task. - """ - load( - new_amp, - table_name="amp_cacem", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="append", - ) - - -with Flow("import amp cacem") as flow: - local_hashes = extract_local_hashes() - remote_hashes = extract_remote_hashes() - outer_hashes = merge_hashes(local_hashes, remote_hashes) - inner_merged = merge_hashes(local_hashes, remote_hashes, "inner") - - ids_to_delete = select_ids_to_delete(outer_hashes) - cond_delete = delete_required(ids_to_delete) - with case(cond_delete, True): - delete(ids_to_delete) - - ids_to_update = select_ids_to_update(inner_merged) - cond_update = update_required(ids_to_update) - with case(cond_update, True): - new_regulations = extract_new_amp(ids_to_update) - update_amps(new_regulations) - - ids_to_insert = select_ids_to_insert(outer_hashes) - cond_insert = insert_required(ids_to_insert) - with case(cond_insert, True): - new_regulations = extract_new_amp(ids_to_insert) - load_new_amps(new_regulations) - - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/amp_ofb.py b/datascience/src/pipeline/flows/amp_ofb.py deleted file mode 100644 index bec3dd2289..0000000000 --- a/datascience/src/pipeline/flows/amp_ofb.py +++ /dev/null @@ -1,268 +0,0 @@ -from io import BytesIO -from pathlib import Path - -import geopandas as gpd -import prefect -from prefect import Flow, task -import requests -from sqlalchemy import text - -from config import AMP_AREAS_URL, LIBRARY_LOCATION, PROXIES -from src.db_config import create_engine -from src.pipeline.generic_tasks import load -from src.pipeline.helpers.spatial import to_multipolygon -from src.pipeline.processing import prepare_df_for_loading -from src.pipeline.utils import psql_insert_copy - -AMP_AREAS_FILE_PATH = LIBRARY_LOCATION / "pipeline/data/amp_areas.zip" - -AMP_AREAS_COLUMNS = [ - "mpa_id", - "mpa_pid", - "gid", - "des_id", - "mpa_status", - "mpa_name", - "mpa_oriname", - "des_desigfr", - "des_desigtype", - "mpa_datebegin", - "mpa_statusyr", - "mpa_wdpaid", - "mpa_wdpapid", - "mpa_mnhnid", - "mpa_marine", - "mpa_calcarea", - "mpa_calcmarea", - "mpa_reparea", - "mpa_repmarea", - "mpa_url", - "mpa_updatewhen", - "iucn_idiucn", - "subloc_code", - "subloc_name", - "country_piso3", - "country_iso3", - "country_iso3namefr", - "geom" - ] - -@task(checkpoint=False) -def extract_amp_areas(url: str, proxies: dict) -> gpd.GeoDataFrame: - """ - Download shapefile of AMP and load to GeoDataFrame. - - Args: - url (str): url to fetch the shapefile from - proxies (dict): http and https proxies to use for the download. - - Returns: - gpd.GeoDataFrame: GeoDataFrame of AMP areas - """ - - r = requests.get(url, proxies=proxies) - r.raise_for_status() - - # The file needs to be downloaded first and loaded as GeoDataFrame from the - # downloaded file, as loading the bytes stream directly to GeoDataFrame results in - # fiona using an incorrect encoding that cannot be enforced in `gpd.read_file` : - # passing `encoding` as keyword argument to `gpd.read_file` results in a conflict - # in fiona when the input is an bytes stream. - with open(AMP_AREAS_FILE_PATH, "wb") as f: - f.write(BytesIO(r.content).read()) - - amp_areas = gpd.read_file(AMP_AREAS_FILE_PATH) - - return amp_areas - - -@task(checkpoint=False) -def transform_amp_areas(amp_areas: gpd.GeoDataFrame) -> gpd.GeoDataFrame: - """ - Transforms the ``amp_areas`` DataFrame to match the desired table columns. - """ - - amp_areas = amp_areas.copy(deep=True) - amp_areas.columns = amp_areas.columns.map(str.lower) - amp_areas = amp_areas.rename(columns={ - "geometry" : "geom", - "nom" : "mpa_name", - "mpa_orinam": "mpa_oriname", - "des_desigf": "des_desigfr", - "des_desigt": "des_desigtype", - "mpa_datebe": "mpa_datebegin", - "mpa_statu0": "mpa_statusyr", - "mpa_wdpapi": "mpa_wdpapid", - "mpa_calcar": "mpa_calcarea", - "mpa_calcma": "mpa_calcmarea", - "mpa_repare": "mpa_reparea", - "mpa_repmar": "mpa_repmarea", - "mpa_update": "mpa_updatewhen", - "iucn_idiuc": "iucn_idiucn", - "subloc_cod": "subloc_code", - "subloc_nam": "subloc_name", - "country_pi": "country_piso3", - "country_is": "country_iso3", - "country_i0": "country_iso3namefr" - }) - amp_areas = amp_areas.set_geometry("geom") - amp_areas["geom"] = amp_areas.geom.map(to_multipolygon) - amp_areas = amp_areas.to_crs("EPSG:4326") - return amp_areas - - -@task(checkpoint=False) -def load_amp_areas(amp_areas: gpd.GeoDataFrame): - - logger = prefect.context.get("logger") - - e = create_engine("cacem_local") - - with e.begin() as connection: - logger.info("Creating temporary table") - connection.execute( - text( - "CREATE TEMP TABLE tmp_amp_ofb(" - " id serial PRIMARY KEY," - " geom geometry," - " mpa_id integer UNIQUE NOT NULL," - " mpa_pid integer," - " gid integer," - " mpa_name text," - " mpa_oriname text," - " des_id integer," - " des_desigfr text," - " des_desigtype text," - " mpa_status text," - " mpa_datebegin text," - " mpa_statusyr integer," - " mpa_wdpaid integer," - " mpa_wdpapid text," - " mpa_mnhnid text," - " mpa_marine integer," - " mpa_calcarea double precision," - " mpa_calcmarea double precision," - " mpa_reparea double precision," - " mpa_repmarea double precision," - " mpa_url text," - " mpa_updatewhen text," - " iucn_idiucn text," - " subloc_code text," - " subloc_name text," - " country_piso3 text," - " country_iso3 text," - " country_iso3namefr text" - ")" - "ON COMMIT DROP;" - ) - ) - - amp_areas = prepare_df_for_loading( - df = amp_areas, - logger = logger, - nullable_integer_columns = [ - "gid", - "mpa_id", - "mpa_pid", - "des_id", - "mpa_statusyr", - "mpa_wdpaid", - "mpa_marine" - ] - ) - - columns_to_load = AMP_AREAS_COLUMNS - - logger.info("Loading amp to temporary table") - - amp_areas[columns_to_load].to_sql( - "tmp_amp_ofb", - connection, - if_exists="append", - index=False, - method=psql_insert_copy, - ) - - logger.info("Updating amp from temporary table") - - updated_rows = connection.execute( - text( - "UPDATE prod.\"Aires marines protégées\" p " - "SET " - " geom = st_multi(st_setsrid(ep.geom,4326)), " - " mpa_pid = ep.mpa_pid, " - " gid = ep.gid, " - " mpa_name = ep.mpa_name, " - " mpa_oriname = ep.mpa_oriname, " - " des_id = ep.des_id, " - " des_desigfr = ep.des_desigfr, " - " des_desigtype = ep.des_desigtype, " - " mpa_status = ep.mpa_status, " - " mpa_datebegin = ep.mpa_datebegin, " - " mpa_statusyr = ep.mpa_statusyr, " - " mpa_wdpaid = ep.mpa_wdpaid, " - " mpa_wdpapid = ep.mpa_wdpapid, " - " mpa_mnhnid = ep.mpa_mnhnid, " - " mpa_marine = ep.mpa_marine, " - " mpa_calcarea = ep.mpa_calcarea, " - " mpa_calcmarea = ep.mpa_calcmarea, " - " mpa_reparea = ep.mpa_reparea, " - " mpa_repmarea = ep.mpa_repmarea, " - " mpa_url = ep.mpa_url, " - " mpa_updatewhen = ep.mpa_updatewhen, " - " iucn_idiucn = ep.iucn_idiucn, " - " subloc_code = ep.subloc_code, " - " subloc_name = ep.subloc_name, " - " country_piso3 = ep.country_piso3, " - " country_iso3 = ep.country_iso3, " - " country_iso3namefr = ep.country_iso3namefr " - "FROM tmp_amp_ofb ep " - "WHERE p.mpa_id = ep.mpa_id;" - ) - ) - logger.info(f"Number of rows updated: {updated_rows.rowcount}") - - logger.info("Delete amp not existing in temporary table") - deleted_rows = connection.execute( - text( - "DELETE FROM prod.\"Aires marines protégées\" p " - "WHERE p.mpa_id NOT IN " - " (SELECT mpa_id FROM tmp_amp_ofb);" - ) - ) - logger.info(f"Number of rows deleted: {deleted_rows.rowcount}") - - logger.info("Insert missing amp from temporary table") - inserted_rows = connection.execute( - text( - "INSERT INTO prod.\"Aires marines protégées\" (" - " geom, mpa_id, mpa_pid, gid, mpa_name, mpa_oriname, " - " des_id, des_desigfr, des_desigtype, mpa_status, " - " mpa_datebegin, mpa_statusyr, mpa_wdpaid, mpa_wdpapid, " - " mpa_mnhnid, mpa_marine, mpa_calcarea, mpa_calcmarea, " - " mpa_reparea, mpa_repmarea, mpa_url, mpa_updatewhen, " - " iucn_idiucn, subloc_code, subloc_name, country_piso3, " - " country_iso3, country_iso3namefr " - " )" - "SELECT " - " st_multi(st_setsrid(geom,4326)), mpa_id, mpa_pid, gid, mpa_name, mpa_oriname, " - " des_id, des_desigfr, des_desigtype, mpa_status, " - " mpa_datebegin, mpa_statusyr, mpa_wdpaid, mpa_wdpapid, " - " mpa_mnhnid, mpa_marine, mpa_calcarea, mpa_calcmarea, " - " mpa_reparea, mpa_repmarea, mpa_url, mpa_updatewhen, " - " iucn_idiucn, subloc_code, subloc_name, country_piso3, " - " country_iso3, country_iso3namefr " - "FROM tmp_amp_ofb " - "WHERE mpa_id NOT IN " - " (SELECT mpa_id FROM prod.\"Aires marines protégées\" WHERE mpa_id is not null);" - ) - ) - logger.info(f"Number of rows inserted: {inserted_rows.rowcount}") - - -with Flow("update amp from ofb") as flow: - amp_areas = extract_amp_areas(url=AMP_AREAS_URL, proxies=PROXIES) - amp_areas = transform_amp_areas(amp_areas) - load_amp_areas(amp_areas) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/beaches.py b/datascience/src/pipeline/flows/beaches.py deleted file mode 100644 index 18dc18cf48..0000000000 --- a/datascience/src/pipeline/flows/beaches.py +++ /dev/null @@ -1,34 +0,0 @@ -from pathlib import Path -import prefect -import geopandas as gpd -from prefect import Flow, task -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_beaches() -> gpd.GeoDataFrame: - return extract( - db_name="cacem_local", - query_filepath="cross/cacem/beaches.sql", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - -@task(checkpoint=False) -def load_beaches(beaches: gpd.GeoDataFrame): - load( - beaches, - table_name="beaches", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - -with Flow("Beaches") as flow: - beaches = extract_beaches() - load_beaches(beaches) - - -flow.file_name = Path(__file__).name \ No newline at end of file diff --git a/datascience/src/pipeline/flows/competence_cross_areas.py b/datascience/src/pipeline/flows/competence_cross_areas.py deleted file mode 100644 index b3555e4d65..0000000000 --- a/datascience/src/pipeline/flows/competence_cross_areas.py +++ /dev/null @@ -1,35 +0,0 @@ -import geopandas as gpd -import prefect -from pathlib import Path -from prefect import Flow, task -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_competence_cross_areas() -> gpd.GeoDataFrame: - return extract( - db_name="cacem_local", - query_filepath="cross/cacem/competence_cross_areas.sql", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - - -@task(checkpoint=False) -def load_competence_cross_areas(competence_cross_areas: gpd.GeoDataFrame): - load( - competence_cross_areas, - table_name="competence_cross_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -with Flow("Competence cross areas") as flow: - competence_cross_areas = extract_competence_cross_areas() - load_competence_cross_areas(competence_cross_areas) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/control_objectives.py b/datascience/src/pipeline/flows/control_objectives.py deleted file mode 100644 index 6ff02b2407..0000000000 --- a/datascience/src/pipeline/flows/control_objectives.py +++ /dev/null @@ -1,34 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, Parameter, case, task -from prefect.executors import LocalDaskExecutor - -from src.pipeline.generic_tasks import load -from src.pipeline.shared_tasks.control_flow import check_flow_not_running -from src.pipeline.shared_tasks.etl import extract_csv_file - - -@task(checkpoint=False) -def load_control_objectives(control_objectives: pd.DataFrame): - load( - control_objectives, - table_name="control_objectives", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -with Flow("Control objectives", executor=LocalDaskExecutor()) as flow: - - file_name = Parameter("file_name", default="control_objectives.csv") - - flow_not_running = check_flow_not_running() - with case(flow_not_running, True): - control_objectives = extract_csv_file(file_name=file_name) - load_control_objectives(control_objectives) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/email_actions_to_units.py b/datascience/src/pipeline/flows/email_actions_to_units.py deleted file mode 100644 index aaf82e101d..0000000000 --- a/datascience/src/pipeline/flows/email_actions_to_units.py +++ /dev/null @@ -1,521 +0,0 @@ -from datetime import datetime, timedelta -from email.message import EmailMessage -from pathlib import Path -from smtplib import ( - SMTPDataError, - SMTPHeloError, - SMTPNotSupportedError, - SMTPRecipientsRefused, - SMTPSenderRefused, -) -from time import sleep -from typing import List - -import css_inline -import pandas as pd -import prefect -from jinja2 import Environment, FileSystemLoader, Template, select_autoescape -from prefect import Flow, Parameter, case, flatten, task, unmapped -from prefect.engine.signals import SKIP -from prefect.executors import LocalDaskExecutor -from prefect.tasks.control_flow import merge - -from config import ( - CACEM_ANALYST_EMAIL, - CACEM_ANALYST_NAME, - CACEM_EMAIL_ADDRESS, - EMAIL_STYLESHEETS_LOCATION, - EMAIL_TEMPLATES_LOCATION, - METABASE_URL, -) -from src.pipeline.entities.actions_emailing import ( - ControlUnit, - ControlUnitActions, - ControlUnitActionsSentMessage, - EnvActionType, -) -from src.pipeline.generic_tasks import extract, load -from src.pipeline.helpers.dates import Period -from src.pipeline.helpers.emails import create_html_email, send_email -from src.pipeline.shared_tasks.control_flow import ( - check_flow_not_running, - filter_results, -) -from src.pipeline.shared_tasks.dates import get_utcnow - - -@task(checkpoint=False) -def get_actions_period( - utcnow: datetime, - start_days_ago: int, - end_days_ago: int, -) -> Period: - - assert isinstance(start_days_ago, int) - assert isinstance(end_days_ago, int) - assert start_days_ago > end_days_ago - - today = utcnow.date() - - start_day = today - timedelta(days=start_days_ago) - end_day = today - timedelta( - days=end_days_ago - 1 - ) # -1 to include the last day - - return Period( - start=datetime( - year=start_day.year, month=start_day.month, day=start_day.day - ), - end=datetime(year=end_day.year, month=end_day.month, day=end_day.day), - ) - - -@task(checkpoint=False) -def extract_env_actions(period: Period) -> pd.DataFrame: - return extract( - "monitorenv_remote", - "monitorenv/env_actions_to_email.sql", - params={ - "min_datetime_utc": period.start, - "max_datetime_utc": period.end, - }, - ) - - -@task(checkpoint=False) -def get_control_unit_ids(env_action: pd.DataFrame) -> List[int]: - # Warning : using `set` and not `.unique()` on `control_unit_id ` in order to - # return `int` and not `numpy.int64` values, which are not handled by psycopg2 when - # passed as query parameters. - return sorted(set(env_action.control_unit_id)) - - -@task(checkpoint=False) -def extract_all_control_units() -> pd.DataFrame: - return extract( - "monitorenv_remote", - "monitorenv/all_control_units.sql", - ) - - -@task(checkpoint=False) -def extract_control_units(control_unit_ids: List[str]) -> pd.DataFrame: - - if len(control_unit_ids) == 0: - raise SKIP("No control units to extract.") - else: - return extract( - "monitorenv_remote", - "monitorenv/control_units.sql", - params={ - "control_unit_ids": tuple(control_unit_ids), - }, - ) - - -@task(checkpoint=False) -def to_control_unit_actions( - env_actions: pd.DataFrame, - period: Period, - control_units: pd.DataFrame, -) -> List[ControlUnitActions]: - - records = control_units.to_dict(orient="records") - control_units = [ControlUnit(**control_unit) for control_unit in records] - - return [ - ControlUnitActions( - control_unit=control_unit, - period=period, - controls=env_actions[ - (env_actions.action_type == EnvActionType.CONTROL.value) - & (env_actions.control_unit_id == control_unit.control_unit_id) - & (~env_actions.is_late_update) - ].reset_index(drop=True), - late_controls=env_actions[ - (env_actions.action_type == EnvActionType.CONTROL.value) - & (env_actions.control_unit_id == control_unit.control_unit_id) - & (env_actions.is_late_update) - ].reset_index(drop=True), - surveillances=env_actions[ - (env_actions.action_type == EnvActionType.SURVEILLANCE.value) - & (env_actions.control_unit_id == control_unit.control_unit_id) - & (~env_actions.is_late_update) - ].reset_index(drop=True), - late_surveillances=env_actions[ - (env_actions.action_type == EnvActionType.SURVEILLANCE.value) - & (env_actions.control_unit_id == control_unit.control_unit_id) - & (env_actions.is_late_update) - ].reset_index(drop=True), - ) - for control_unit in control_units - ] - - -@task(checkpoint=False) -def get_template() -> Template: - templates_locations = [ - EMAIL_TEMPLATES_LOCATION, - EMAIL_STYLESHEETS_LOCATION, - ] - - env = Environment( - loader=FileSystemLoader(templates_locations), - autoescape=select_autoescape(), - ) - - return env.get_template("email_actions_to_units.jinja") - - -@task(checkpoint=False) -def render( - control_unit_actions: ControlUnitActions, template: Template -) -> str: - def format_themes(themes: dict) -> str: - - formatted_themes = [ - f"{theme} ({', '.join(subthemes)})" - for (theme, subthemes) in themes.items() - ] - - return ", ".join(formatted_themes) - - def render_controls(controls: pd.DataFrame) -> str: - controls = controls.copy(deep=True) - - controls["number_of_controls"] = controls.number_of_controls.fillna( - 0 - ).astype(int) - controls["infraction"] = controls.infraction.map( - {True: "Oui", False: "Non"}, na_action="ignore" - ).fillna("-") - controls[ - "action_start_datetime_utc" - ] = controls.action_start_datetime_utc.map( - lambda d: d.strftime("%Y-%m-%d %H:%M") - ) - controls["mission_type"] = controls.mission_type.map( - {"SEA": "Mer", "LAND": "Terre", "AIR": "Air"} - ) - controls["themes"] = controls.themes.map(format_themes) - - columns = { - "action_start_datetime_utc": "Date du contrôle", - "mission_type": "Type de mission", - "action_facade": "Façade", - "action_department": "Département", - "infraction": "Infraction relevée", - "number_of_controls": "Nombre de contrôles", - "themes": "Thématique(s) de contrôle", - } - - controls = controls[columns.keys()].rename(columns=columns) - controls = controls.to_html(index=False, border=1) - return controls - - def render_surveillances(surveillances: pd.DataFrame) -> str: - surveillances = surveillances.copy(deep=True) - surveillances[ - "action_start_datetime_utc" - ] = surveillances.action_start_datetime_utc.map( - lambda d: d.strftime("%Y-%m-%d %H:%M") - ) - surveillances[ - "action_end_datetime_utc" - ] = surveillances.action_end_datetime_utc.map( - lambda d: d.strftime("%Y-%m-%d %H:%M") - ) - surveillances["mission_type"] = surveillances.mission_type.map( - {"SEA": "Mer", "LAND": "Terre", "AIR": "Air"} - ) - surveillances["themes"] = surveillances.themes.map(format_themes) - - columns = { - "action_start_datetime_utc": "Début de surveillance", - "action_end_datetime_utc": "Fin de surveillance", - "mission_type": "Type de mission", - "action_facade": "Façade", - "action_department": "Département", - "surveillance_duration": "Durée (h)", - "themes": "Thématique(s) de surveillance", - } - - surveillances = surveillances[columns.keys()].rename(columns=columns) - surveillances = surveillances.to_html(index=False, border=1) - return surveillances - - if len(control_unit_actions.controls) > 0: - controls = render_controls(control_unit_actions.controls) - else: - controls = "Aucun" - - if len(control_unit_actions.surveillances) > 0: - surveillances = render_surveillances( - control_unit_actions.surveillances - ) - else: - surveillances = "Aucune" - - if len(control_unit_actions.late_controls) > 0: - late_controls = render_controls(control_unit_actions.late_controls) - else: - late_controls = "Aucun" - - if len(control_unit_actions.late_surveillances) > 0: - late_surveillances = render_surveillances( - control_unit_actions.late_surveillances - ) - else: - late_surveillances = "Aucune" - - html = template.render( - control_unit_name=control_unit_actions.control_unit.control_unit_name, - controls=controls, - late_controls=late_controls, - surveillances=surveillances, - late_surveillances=late_surveillances, - from_date=control_unit_actions.period.start.strftime( - "%d/%m/%Y %H:%M UTC" - ), - to_date=control_unit_actions.period.end.strftime("%d/%m/%Y %H:%M UTC"), - metabase_url=METABASE_URL, - cacem_email_address=CACEM_EMAIL_ADDRESS, - cacem_analyst_name=CACEM_ANALYST_NAME, - cacem_analyst_email=CACEM_ANALYST_EMAIL, - ) - - html = css_inline.inline(html) - return html - - -@task(checkpoint=False) -def create_email( - html: str, actions: ControlUnitActions, test_mode: bool -) -> EmailMessage: - - to = ( - CACEM_EMAIL_ADDRESS - if test_mode - else actions.control_unit.email_addresses - ) - - message = create_html_email( - to=to, - subject="Bilan hebdomadaire contrôle de l'environnement marin", - html=html, - reply_to=CACEM_EMAIL_ADDRESS, - ) - - return message - - -@task(checkpoint=False) -def send_env_actions_email( - message: EmailMessage, actions: ControlUnitActions, is_integration: bool -) -> List[ControlUnitActionsSentMessage]: - """ - Sends input email using the contents of `From` header as sender and `To`, `Cc` - and `Bcc` headers as recipients. - - Args: - message (EmailMessage): email message to send - actions (ControlUnitActions): `ControlUnitActions` related to message - is_integration (bool): if ``False``, the message is not actually sent - - Returns: - List[ControlUnitActionsSentMessage]: List of sent messages and their error - codes, if any. - """ - - logger = prefect.context.get("logger") - addressees = actions.control_unit.email_addresses - - try: - try: - if is_integration: - logger.info(("(Mock) Sending env actions.")) - send_errors = {} - else: - logger.info(("Sending env actions.")) - send_errors = send_email(message) - except (SMTPHeloError, SMTPDataError): - # Retry - logger.warning("Message not sent, retrying...") - sleep(10) - send_errors = send_email(message) - except SMTPHeloError: - send_errors = { - addr: ( - None, - "The server didn't reply properly to the helo greeting.", - ) - for addr in addressees - } - logger.error(str(send_errors)) - except SMTPRecipientsRefused: - # All recipients were refused - send_errors = { - addr: ( - None, - "The server rejected ALL recipients (no mail was sent)", - ) - for addr in addressees - } - logger.error(str(send_errors)) - except SMTPSenderRefused: - send_errors = { - addr: ( - None, - "The server didn't accept the from_addr.", - ) - for addr in addressees - } - logger.error(str(send_errors)) - except SMTPDataError: - send_errors = { - addr: ( - None, - ( - "The server replied with an unexpected error code " - "(other than a refusal of a recipient)." - ), - ) - for addr in addressees - } - logger.error(str(send_errors)) - except SMTPNotSupportedError: - send_errors = { - addr: ( - None, - ( - "The mail_options parameter includes 'SMTPUTF8' but the SMTPUTF8 " - "extension is not supported by the server." - ), - ) - for addr in addressees - } - logger.error(str(send_errors)) - except ValueError: - send_errors = { - addr: ( - None, - "there is more than one set of 'Resent-' headers.", - ) - for addr in addressees - } - logger.error(str(send_errors)) - except Exception: - send_errors = {addr: (None, "Unknown error.") for addr in addressees} - logger.error(str(send_errors)) - - now = datetime.utcnow() - - sent_messages = [] - - for addressee in addressees: - if addressee in send_errors: - success = False - error_code = send_errors[addressee][0] - error_message = send_errors[addressee][1] - else: - success = True - error_code = None - error_message = None - - sent_messages.append( - ControlUnitActionsSentMessage( - control_unit_id=actions.control_unit.control_unit_id, - control_unit_name=actions.control_unit.control_unit_name, - email_address=addressee, - sending_datetime_utc=now, - actions_min_datetime_utc=actions.period.start, - actions_max_datetime_utc=actions.period.end, - number_of_actions=len(actions.controls) - + len(actions.surveillances) - + len(actions.late_controls) - + len(actions.late_surveillances), - success=success, - error_code=error_code, - error_message=error_message, - ) - ) - return sent_messages - - -@task(checkpoint=False) -def control_unit_actions_list_to_df( - messages: List[ControlUnitActionsSentMessage], -) -> pd.DataFrame: - messages = pd.DataFrame(messages) - return messages - - -@task(checkpoint=False) -def load_emails_sent_to_control_units( - emails_sent_to_control_units: pd.DataFrame, -): - load( - emails_sent_to_control_units, - table_name="emails_sent_to_control_units", - schema="public", - db_name="monitorenv_remote", - how="replace", - nullable_integer_columns=["error_code"], - logger=prefect.context.get("logger"), - ) - - -with Flow("Email actions to units", executor=LocalDaskExecutor()) as flow: - flow_not_running = check_flow_not_running() - with case(flow_not_running, True): - test_mode = Parameter("test_mode") - is_integration = Parameter("is_integration") - start_days_ago = Parameter("start_days_ago") - end_days_ago = Parameter("end_days_ago") - email_all_units = Parameter("email_all_units") - - template = get_template() - utcnow = get_utcnow() - - period = get_actions_period( - utcnow=utcnow, - start_days_ago=start_days_ago, - end_days_ago=end_days_ago, - ) - env_actions = extract_env_actions(period=period) - - with case(email_all_units, True): - all_control_units = extract_all_control_units() - - with case(email_all_units, False): - control_unit_ids = get_control_unit_ids(env_actions) - selected_control_units = extract_control_units(control_unit_ids) - - control_units = merge( - all_control_units, selected_control_units, checkpoint=False - ) - - control_unit_actions = to_control_unit_actions( - env_actions, period, control_units - ) - - html = render.map(control_unit_actions, template=unmapped(template)) - - message = create_email.map( - html=html, - actions=control_unit_actions, - test_mode=unmapped(test_mode), - ) - message = filter_results(message) - - sent_messages = send_env_actions_email.map( - message, - control_unit_actions, - is_integration=unmapped(is_integration), - ) - - sent_messages = flatten(sent_messages) - sent_messages = control_unit_actions_list_to_df(sent_messages) - load_emails_sent_to_control_units(sent_messages) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/facade_areas.py b/datascience/src/pipeline/flows/facade_areas.py deleted file mode 100644 index c88be30605..0000000000 --- a/datascience/src/pipeline/flows/facade_areas.py +++ /dev/null @@ -1,43 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, task - -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_facade_areas() -> pd.DataFrame: - """ - Extract facade areas from the monitorfish_local (cross) database as a DataFrame. - - Returns: - pd.DataFrame: GeoDataFrame of facade areas - """ - - return extract( - db_name="monitorfish_local", query_filepath="cross/cnsp/facade_areas.sql" - ) - - -@task(checkpoint=False) -def load_facade_areas(facade_areas: pd.DataFrame): - - logger = prefect.context.get("logger") - - load( - facade_areas, - table_name="facade_areas_subdivided", - schema="public", - db_name="monitorenv_remote", - logger=logger, - how="replace", - ) - - -with Flow("Facade areas") as flow: - facade_areas = extract_facade_areas() - load_facade_areas(facade_areas) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/facade_areas_unextended.py b/datascience/src/pipeline/flows/facade_areas_unextended.py deleted file mode 100644 index f17a0d88d0..0000000000 --- a/datascience/src/pipeline/flows/facade_areas_unextended.py +++ /dev/null @@ -1,43 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, task - -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_facade_areas_unextended() -> pd.DataFrame: - """ - Extract facade areas unextended from the monitorfish_local (cross) database as a DataFrame. - - Returns: - pd.DataFrame: GeoDataFrame of facade areas - """ - - return extract( - db_name="monitorfish_local", query_filepath="cross/cnsp/facade_areas_unextended.sql" - ) - - -@task(checkpoint=False) -def load_facade_areas_unextended(facade_areas_unextended: pd.DataFrame): - - logger = prefect.context.get("logger") - - load( - facade_areas_unextended, - table_name="facade_areas_unextended", - schema="public", - db_name="monitorenv_remote", - logger=logger, - how="replace", - ) - - -with Flow("Facade areas unextended") as flow: - facade_areas_unextended = extract_facade_areas_unextended() - load_facade_areas_unextended(facade_areas_unextended) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/fao_areas.py b/datascience/src/pipeline/flows/fao_areas.py deleted file mode 100644 index ae5871d257..0000000000 --- a/datascience/src/pipeline/flows/fao_areas.py +++ /dev/null @@ -1,82 +0,0 @@ -from io import BytesIO -from pathlib import Path - -import geopandas as gpd -import prefect -import requests -from prefect import Flow, task - -from config import FAO_AREAS_URL, LIBRARY_LOCATION, PROXIES -from src.pipeline.generic_tasks import load -from src.pipeline.helpers.spatial import to_multipolygon - -FAO_AREAS_FILE_PATH = LIBRARY_LOCATION / "pipeline/data/fao_areas.zip" - - -@task(checkpoint=False) -def extract_fao_areas(url: str, proxies: dict) -> gpd.GeoDataFrame: - """ - Download shapefile of FAO areas and load to GeoDataFrame. - - Args: - url (str): url to fetch the shapefile from - proxies (dict): http and https proxies to use for the download. - - Returns: - gpd.GeoDataFrame: GeoDataFrame of FAO areas - """ - - r = requests.get(url, proxies=proxies) - r.raise_for_status() - - # The file needs to be downloaded first and loaded as GeoDataFrame from the - # downloaded file, as loading the bytes stream directly to GeoDataFrame results in - # fiona using an incorrect encoding that cannot be enforced in `gpd.read_file` : - # passing `encoding` as keyword argument to `gpd.read_file` results in a conflict - # in fiona when the input is an bytes stream. - with open(FAO_AREAS_FILE_PATH, "wb") as f: - f.write(BytesIO(r.content).read()) - - fao_areas = gpd.read_file(FAO_AREAS_FILE_PATH) - - return fao_areas - - -@task(checkpoint=False) -def transform_fao_areas(fao_areas: gpd.GeoDataFrame) -> gpd.GeoDataFrame: - """ - Transforms the ``fao_areas`` DataFrame to match the desired table columns. - """ - - fao_areas = fao_areas.copy(deep=True) - fao_areas.columns = fao_areas.columns.map(str.lower) - fao_areas = fao_areas.drop(columns=["id"]) - fao_areas = gpd.GeoDataFrame(fao_areas) - fao_areas = fao_areas.rename(columns={"geometry": "wkb_geometry"}) - fao_areas = fao_areas.set_geometry("wkb_geometry") - fao_areas["wkb_geometry"] = fao_areas.wkb_geometry.map(to_multipolygon) - - return fao_areas - - -@task(checkpoint=False) -def load_fao_areas(fao_areas: gpd.GeoDataFrame): - - logger = prefect.context.get("logger") - - load( - fao_areas, - table_name="fao_areas", - schema="public", - db_name="monitorenv_remote", - logger=logger, - how="replace", - ) - - -with Flow("FAO areas") as flow: - fao_areas = extract_fao_areas(url=FAO_AREAS_URL, proxies=PROXIES) - fao_areas = transform_fao_areas(fao_areas) - load_fao_areas(fao_areas) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/historic_control_units.py b/datascience/src/pipeline/flows/historic_control_units.py deleted file mode 100644 index 19180c9d5b..0000000000 --- a/datascience/src/pipeline/flows/historic_control_units.py +++ /dev/null @@ -1,131 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, Parameter, task -from sqlalchemy import DDL - -from config import HISTORIC_CONTROL_UNITS_MAX_ID -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_historic_control_units() -> pd.DataFrame: - """ - Extract hitoric control units from the fmc database. - - Returns: - pd.DataFrame: DataFrame historic control units - """ - - return extract(db_name="fmc", query_filepath="fmc/control_units.sql") - - -@task(checkpoint=False) -def transform_control_units(control_units: pd.DataFrame) -> pd.DataFrame: - """ - Adds ' (historique)' suffix to control_unit names to avoid conflicts with similarly - named control units in the new control units reposity, and a 'deleted' field with - `True` as values. - - Args: - control_units (pd.DataFrame): Historic control units. - - Returns: - pd.DataFrame: Modified `control_units` DataFrame - """ - control_units = control_units.copy(deep=True) - - # Add " [x]" suffix to duplicated control unit names. - control_units["name_occurence"] = ( - control_units.groupby("name")["id"].rank().astype(int) - ) - control_units["name_suffix"] = control_units["name_occurence"].map( - lambda s: f" [{s}]" if s > 1 else "" - ) - control_units["name"] = control_units[["name", "name_suffix"]].apply( - lambda row: row["name"] + row["name_suffix"], axis=1 - ) - control_units = control_units.drop( - columns=["name_occurence", "name_suffix"] - ) - - # Add " (historique)" suffix to control units imported from Poseidon. - control_units["name"] = control_units.name.map( - lambda s: s + " (historique)" - ) - control_units["archived"] = True - return control_units - - -@task(checkpoint=False) -def check_id_range(control_units: pd.DataFrame, max_id: int) -> pd.DataFrame: - """ - Raises `ValueError` if one or more of the `id`s in the input `control_units` - DataFrame exceed `max_id`. Otherwise returns `control_units`. - - This is done to check that the ids of the historic control units imported do not - overlap with the ids of the new control units. - - Args: - control_units (pd.DataFrame): Historic control units, with an `id` column - max_id (int): Max id reserved for historic control units in the database. - - Raises: - ValueError: If max(control_units.id) exceeds `max_id` - - Returns: - pd.DataFrame: Same as input `control_units` - """ - try: - assert control_units.id.max() <= max_id - except AssertionError: - raise ValueError( - ( - "Control units ids exceed the maximum allowed for historic control " - f"units. The id should be less than {max_id} but the ids in the data " - f"reach {control_units.id.max()}" - ) - ) - - return control_units - - -@task(checkpoint=False) -def load_historic_control_units(control_units: pd.DataFrame): - - logger = prefect.context.get("logger") - - load( - control_units, - table_name="control_units", - schema="public", - db_name="monitorenv_remote", - logger=logger, - how="upsert", - df_id_column="id", - table_id_column="id", - init_ddls=[ - DDL( - "ALTER TABLE public.missions_control_units " - "DROP CONSTRAINT missions_control_units_control_unit_id_fkey;" - ) - ], - end_ddls=[ - DDL( - "ALTER TABLE public.missions_control_units " - "ADD CONSTRAINT missions_control_units_control_unit_id_fkey " - "FOREIGN KEY (control_unit_id) REFERENCES public.control_units (id);" - ) - ], - ) - - -with Flow("Historic control units") as flow: - max_id = Parameter("max_id", default=HISTORIC_CONTROL_UNITS_MAX_ID) - control_units = extract_historic_control_units() - control_units = transform_control_units(control_units) - control_units = check_id_range(control_units, max_id=max_id) - load_historic_control_units(control_units) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/historic_controls.py b/datascience/src/pipeline/flows/historic_controls.py deleted file mode 100644 index c4586a956b..0000000000 --- a/datascience/src/pipeline/flows/historic_controls.py +++ /dev/null @@ -1,414 +0,0 @@ -import uuid -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, task -from sqlalchemy import DDL - -from config import POSEIDON_CACEM_MISSION_ID_TO_MONITORENV_MISSION_ID_SHIFT -from src.db_config import create_engine -from src.pipeline.generic_tasks import extract, load -from src.pipeline.processing import zeros_ones_to_bools - -# dictionnaire des correspondances pour les nouveaux Themes -dict_new_themes = { - "Pêche à pied": "Pêche à pied", - "Rejets illicites (carénage ou macro-déchets)": "Rejets illicites", - "Police des mouillages": "Police des mouillages", - "Police des espèces protégées et de leurs habitats": ( - "Police des espèces protégées et de leurs habitats (faune et flore)" - ), - "Activités et manifestations soumises à évaluation d'incidence Natura 2000": ( - "Activités et manifestations soumises à évaluation d'incidence Natura 2000" - ), - "Pêche de loisir": "Pêche de loisir", - "Domanialité publique dont circulation": "Domanialité publique dont circulation", - ( - "Lutte contre la pollution due aux opérations d'exploitation / " - "exploration / immersion / incinération en mer" - ): "Rejets illicites", - "Lutte contre la pollution par les opérations d'immersion et d'incinération": ( - "Rejets illicites" - ), - "Cultures marines": "Police des activités de cultures marines", - "Travaux en milieu marin (granulats)": "Travaux en milieu marin", - "Travaux en milieu marin (dragage, clapage, infrastructures)": ( - "Travaux en milieu marin" - ), - "Arrêtés PREMAR": "Arrêté à visa environnemental", - "Police des aires marines protégées": "Police des parcs nationaux", - "Police de la chasse en mer": "Pêche de loisir", -} - -# dictionnaire des correspondances pour les nouvelles façades -dict_new_facades = { - "DIRM SA": "SA", - "DM Guyane": "Guyane", - "DM Martinique": "Martinique", - "DM Guadeloupe": "Guadeloupe", - "DM SOI": "Sud Océan Indien", - "DIRM Med": "MED", - "DIRM MEMN": "MEMN", - "DIRM NAMO": "NAMO", - "DTAM St Pierre et Miquelon": None, - "SAM Nouvelle-Calédonie": None, - "SAM Polynésie française": None, -} - - -def str_to_list(s: str, sep: str = ",") -> list: - """ - Split input string at `sep` instances, return list of elements between `sep` - instances. - - Args: - s (str): string to separate. - sep (str, optional): Separator. Defaults to ",". - - Returns: - list: list of strings - """ - if isinstance(s, str): - res = s.split(sep) - else: - res = [] - return res - - -def make_infractions(natinf: list) -> list[dict]: - """create infractions dictionary - - Args: - natinf (list): a list of string natinfs - - Returns: - list[dict]: return a list of infractions (infractions are dictionaries) - """ - inf = [] - if natinf is None: - inf = [] - else: - for i in range(len(natinf)): - inf.append( - { - "id": (uuid.uuid4()), - "natinf": (natinf[i]).split(), - "toProcess": False, - } - ) - return inf - - -def make_themes(row: pd.Series) -> list[dict]: - """ - Take a pandas Series with `themes` and `protected_species` entries, split - `themes` from a string into a list of distinct string themes, then map the new - themes names and create a dictionnary for each theme. - - Args: - row (pd.Seris): pandas Series with `themes` and `protected_species` entries - - Returns: - list[dict]: return a list of themes - """ - - themes = str_to_list(row.themes, sep="") - lthemes = [] - for item in themes: - lthemes.append( - { - "theme": dict_new_themes.get(item), - "protected_species": row.protected_species, - } - ) - return lthemes - - -@task(checkpoint=False) -def extract_historic_controls() -> pd.DataFrame: - """ - Extract hitoric control from the fmc database. - - Returns: - pd.DataFrame: DataFrame historic control - """ - - return extract(db_name="fmc", query_filepath="fmc/historic_controls.sql") - - -@task(checkpoint=False) -def extract_historic_missions() -> pd.DataFrame: - """ - Extract hitoric missions from the fmc database. - Returns: - pd.DataFrame: DataFrame historic missions - """ - return extract(db_name="fmc", query_filepath="fmc/historic_missions.sql") - - -@task(checkpoint=False) -def extract_historic_missions_units() -> pd.DataFrame: - """ - Extract hitoric missions from the fmc database. - Returns: - pd.DataFrame: DataFrame historic missions - """ - return extract( - db_name="fmc", query_filepath="fmc/historic_missions_units.sql" - ) - - -@task(checkpoint=False) -def make_env_actions(historic_controls: pd.DataFrame) -> pd.DataFrame: - - """ - Transform historic controls from Poseidon into the monitorenv env_actions schema - structure. - """ - historic_controls = historic_controls.copy(deep=True) - historic_controls["mission_id"] = ( - historic_controls["mission_id"] - + POSEIDON_CACEM_MISSION_ID_TO_MONITORENV_MISSION_ID_SHIFT - ) - - historic_controls["id"] = historic_controls.apply( - lambda x: uuid.uuid4(), axis=1 - ) - - # séparation des natinfs - historic_controls["natinf"] = historic_controls["natinf"].map( - str_to_list, na_action="ignore" - ) - historic_controls["infractions"] = historic_controls["natinf"].map( - make_infractions - ) - - # Preparing protected species - historic_controls["protected_species"] = historic_controls[ - "protected_species" - ].map(str_to_list, na_action="ignore") - - historic_controls["protected_species"] = historic_controls[ - "protected_species" - ].map(lambda y: y if isinstance(y, list) else []) - - historic_controls["themes"] = historic_controls.apply(make_themes, axis=1) - - historic_controls["value"] = historic_controls.apply( - lambda x: ( - { - "themes": x.themes, - "infractions": x.infractions, - "vehicleType": None, - "actionTargetType": None, - "actionNumberOfControls": x.action_number_of_controls, - } - ), - axis=1, - ) - - # fin du preprocessing - historic_controls.drop( - ["action_number_of_controls", "themes", "natinf", "infractions"], - axis=1, - inplace=True, - ) - historic_controls = historic_controls.loc[ - :, - [ - "id", - "mission_id", - "action_type", - "value", - "action_start_datetime_utc", - ], - ] - - return historic_controls - - -@task(checkpoint=False) -def make_env_missions(missions_poseidon: pd.DataFrame) -> pd.DataFrame: - """ - Transform historic missions from Poseidon into the monitorenv missions schema - structure. - - Args: - missions_poseidon (pd.DataFrame) - - Returns: - pd.DataFrame - """ - missions_poseidon = missions_poseidon.copy(deep=True) - missions_poseidon["id"] = ( - missions_poseidon["id"] - + POSEIDON_CACEM_MISSION_ID_TO_MONITORENV_MISSION_ID_SHIFT - ) - missions_poseidon["mission_types"] = missions_poseidon["mission_type"].map( - str_to_list, na_action="ignore" - ) - - missions_poseidon = missions_poseidon.drop(columns=["mission_type"]) - - missions_poseidon["facade"] = missions_poseidon["facade"].map( - dict_new_facades - ) - missions_poseidon["deleted"] = False - - return missions_poseidon - - -@task(checkpoint=False) -def make_env_mission_units(mission_units: pd.DataFrame) -> pd.DataFrame: - """ - Transform historic missions units from Poseidon into the monitorenv env_actions - schema structure. - - Args: - mission_units (pd.DataFrame) - - Returns: - pd.DataFrame - """ - mission_units = mission_units.copy(deep=True) - mission_units["mission_id"] = ( - mission_units["mission_id"] - + POSEIDON_CACEM_MISSION_ID_TO_MONITORENV_MISSION_ID_SHIFT - ) - - return mission_units - - -@task(checkpoint=False) -def delete_if_missing_mission( - df: pd.DataFrame, missions: pd.DataFrame -) -> pd.DataFrame: - """ - Delete rows from df whose mission_id does not exist in missions dataframe. - - Args: - df (pd.DataFrame): pandas DataFrame. Must have 'mission_id' column. - missions (pd.DataFrame): pandas DataFrame. Must have 'id' column. - - Returns: - pd.DataFrame : filtered df - """ - - df = df.copy(deep=True) - df = df.loc[df.mission_id.isin(missions.id.values)] - - return df - - -@task(checkpoint=False) -def load_missions_and_missions_control_units( - historic_controls: pd.DataFrame, - missions: pd.DataFrame, - missions_control_units: pd.DataFrame, -): - - # In "upsert" loading mode, we want to replace only the missions whose `id` is - # present in the DataFrame. So we use `id` as the identifier. - - e = create_engine("monitorenv_remote") - with e.begin() as connection: - - load( - missions, - table_name="missions", - schema="public", - connection=connection, - table_id_column="mission_source", - logger=prefect.context.get("logger"), - how="upsert", - pg_array_columns=["mission_types"], - df_id_column="mission_source", - init_ddls=[ - DDL( - "ALTER TABLE public.env_actions " - "DROP CONSTRAINT env_actions_mission_id_fkey;" - ), - DDL( - "ALTER TABLE public.env_actions " - "ADD CONSTRAINT env_actions_mission_id_fkey " - "FOREIGN KEY (mission_id) " - "REFERENCES public.missions(id) " - "ON DELETE CASCADE;" - ), - DDL( - "ALTER TABLE public.missions_control_units " - "DROP CONSTRAINT missions_control_units_mission_id_fkey;" - ), - DDL( - "ALTER TABLE public.missions_control_units " - "ADD CONSTRAINT missions_control_units_mission_id_fkey " - "FOREIGN KEY (mission_id) " - "REFERENCES public.missions(id) " - "ON DELETE CASCADE;" - ), - ], - end_ddls=[ - DDL( - "ALTER TABLE public.env_actions " - "DROP CONSTRAINT env_actions_mission_id_fkey;" - ), - DDL( - "ALTER TABLE public.env_actions " - "ADD CONSTRAINT env_actions_mission_id_fkey " - "FOREIGN KEY (mission_id) " - "REFERENCES public.missions (id);" - ), - DDL( - "ALTER TABLE public.missions_control_units " - "DROP CONSTRAINT missions_control_units_mission_id_fkey;" - ), - DDL( - "ALTER TABLE public.missions_control_units " - "ADD CONSTRAINT missions_control_units_mission_id_fkey " - "FOREIGN KEY (mission_id) " - "REFERENCES public.missions(id);" - ), - ], - ) - - load( - historic_controls, - table_name="env_actions", - schema="public", - logger=prefect.context.get("logger"), - jsonb_columns=["value"], - how="append", - connection=connection, - ) - - load( - missions_control_units, - table_name="missions_control_units", - schema="public", - connection=connection, - logger=prefect.context.get("logger"), - how="append", - ) - - -with Flow("Historic controls") as flow: - # Extract - controls = extract_historic_controls() - missions = extract_historic_missions() - missions_units = extract_historic_missions_units() - - # Transform - controls = delete_if_missing_mission(controls, missions) - missions_units = delete_if_missing_mission(missions_units, missions) - - controls = make_env_actions(controls) - missions = make_env_missions(missions) - missions_units = make_env_mission_units(missions_units) - - # Load - load_missions_and_missions_control_units( - controls, missions, missions_units - ) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/infractions.py b/datascience/src/pipeline/flows/infractions.py deleted file mode 100644 index 3715eb8209..0000000000 --- a/datascience/src/pipeline/flows/infractions.py +++ /dev/null @@ -1,45 +0,0 @@ -from pathlib import Path - -import prefect -from prefect import Flow, case, task -from prefect.executors import LocalDaskExecutor - -from src.pipeline.generic_tasks import extract, load -from src.pipeline.shared_tasks.control_flow import check_flow_not_running - - -@task(checkpoint=False) -def extract_infractions(): - return extract("fmc", "fmc/natinf.sql") - - -@task(checkpoint=False) -def clean_infractions(infractions): - infractions.loc[:, "infraction"] = infractions.infraction.map( - str.capitalize - ) - return infractions - - -@task(checkpoint=False) -def load_infractions(infractions): - load( - infractions, - table_name="natinfs", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -with Flow("infractions", executor=LocalDaskExecutor()) as flow: - - flow_not_running = check_flow_not_running() - with case(flow_not_running, True): - - infractions = extract_infractions() - infractions = clean_infractions(infractions) - load_infractions(infractions) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/localized_areas.py b/datascience/src/pipeline/flows/localized_areas.py deleted file mode 100644 index edc00beff9..0000000000 --- a/datascience/src/pipeline/flows/localized_areas.py +++ /dev/null @@ -1,40 +0,0 @@ -from pathlib import Path -import pandas as pd -import prefect -import geopandas as gpd -from prefect import Flow, case, task -from src.pipeline.generic_tasks import delete_rows, extract, load -from src.pipeline.processing import df_values_to_psql_arrays -from sqlalchemy import text -from src.db_config import create_engine -from src.pipeline.utils import psql_insert_copy - - -@task(checkpoint=False) -def extract_localized_areas() -> gpd.GeoDataFrame: - return extract( - db_name="cacem_local", - query_filepath="cross/cacem/localized_areas.sql", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - -@task(checkpoint=False) -def load_localized_areas(localized_areas: gpd.GeoDataFrame): - load( - localized_areas, - table_name="localized_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - pg_array_columns=['control_unit_ids', 'amp_ids'], - how="replace", - ) - -with Flow("Localized Areas") as flow: - localized_areas = extract_localized_areas() - load_localized_areas(localized_areas) - - -flow.file_name = Path(__file__).name \ No newline at end of file diff --git a/datascience/src/pipeline/flows/marpol.py b/datascience/src/pipeline/flows/marpol.py deleted file mode 100644 index 92f30d0642..0000000000 --- a/datascience/src/pipeline/flows/marpol.py +++ /dev/null @@ -1,35 +0,0 @@ - -from pathlib import Path -import geopandas as gpd -import prefect -from prefect import Flow, task - -from src.pipeline.generic_tasks import extract, load - -@task(checkpoint=False) -def extract_marpol() -> gpd.GeoDataFrame: - return extract( - db_name="cacem_local", - query_filepath="cross/cacem/marpol.sql", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - -@task(checkpoint=False) -def load_marpol(marpol: gpd.GeoDataFrame): - load( - marpol, - table_name="marpol", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - -with Flow("Marpol") as flow: - - marpol = extract_marpol() - load_marpol(marpol) - -flow.file_name = Path(__file__).name \ No newline at end of file diff --git a/datascience/src/pipeline/flows/refresh_materialized_view.py b/datascience/src/pipeline/flows/refresh_materialized_view.py deleted file mode 100644 index 47cab09559..0000000000 --- a/datascience/src/pipeline/flows/refresh_materialized_view.py +++ /dev/null @@ -1,34 +0,0 @@ -from pathlib import Path - -import pandas as pd -from prefect import Flow, Parameter, case, task -from prefect.executors import LocalDaskExecutor -from sqlalchemy import Table, text - -from src.db_config import create_engine -from src.pipeline.shared_tasks.control_flow import check_flow_not_running -from src.pipeline.shared_tasks.infrastructure import get_table - - -@task(checkpoint=False) -def refresh_view(view: Table) -> pd.DataFrame: - - assert isinstance(view, Table) - - query = text(f"REFRESH MATERIALIZED VIEW {view.schema}.{view.name}") - e = create_engine("monitorenv_remote") - with e.begin() as connection: - connection.execute(query) - - -with Flow("Refresh materialized view", executor=LocalDaskExecutor()) as flow: - - flow_not_running = check_flow_not_running() - with case(flow_not_running, True): - - view_name = Parameter("view_name") - schema = Parameter("schema", "public") - view = get_table(table_name=view_name, schema=schema) - refresh_view(view) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/regulations.py b/datascience/src/pipeline/flows/regulations.py deleted file mode 100644 index 660ec00ac5..0000000000 --- a/datascience/src/pipeline/flows/regulations.py +++ /dev/null @@ -1,246 +0,0 @@ -import pandas as pd -import prefect -from pathlib import Path -from prefect import Flow, case, task -from sqlalchemy import DDL, text -from src.db_config import create_engine -from src.pipeline.generic_tasks import delete_rows, extract, load -from src.pipeline.shared_tasks.update_queries import delete_required, insert_required, merge_hashes, select_ids_to_delete, select_ids_to_insert, select_ids_to_update, update_required -from src.pipeline.utils import psql_insert_copy -from prefect.tasks.control_flow import merge - -@task(checkpoint=False) -def extract_local_hashes() -> pd.DataFrame: - """ - Extract regulations hashes from cacem - - Returns: - pd.DataFrame: GeoDataFrame of regulation ids + row_hash - """ - return extract( - db_name="cacem_local", query_filepath="cross/cacem/regulations_hashes.sql" - ) - - -@task(checkpoint=False) -def extract_remote_hashes() -> pd.DataFrame: - """ - Extract regulations hashes from monitorenv - - Returns: - pd.DataFrame: GeoDataFrame of regulation ids + row_hash - """ - return extract( - db_name="monitorenv_remote", - query_filepath="monitorenv/regulations_hashes.sql", - ) - -@task(checkpoint=False) -def delete(ids_to_delete: set): - logger = prefect.context.get("logger") - delete_rows( - table_name="regulations_cacem", - schema="public", - db_name="monitorenv_remote", - table_id_column="id", - ids_to_delete=ids_to_delete, - logger=logger, - ) - - -@task(checkpoint=False) -def extract_new_regulatory_areas(ids_to_update: set) -> pd.DataFrame: - return extract( - "cacem_local", - "cross/cacem/regulations.sql", - params={"ids": tuple(ids_to_update)}, - ) - -@task(checkpoint=False) -def update_regulatory_areas(new_regulatory_areas: pd.DataFrame): - """Load the output of ``extract_rows_to_update`` task into ``regulations`` - table. - - Args: - new_regulatory_areas (pd.DataFrame): output of ``extract_rows_to_update`` task. - """ - e = create_engine("monitorenv_remote") - logger = prefect.context.get("logger") - - with e.begin() as connection: - logger.info("Creating temporary table") - connection.execute( - text( - """CREATE TEMP TABLE tmp_regulations_cacem( - id serial, - geom public.geometry(MultiPolygon,4326), - entity_name character varying, - url character varying, - layer_name character varying, - facade character varying, - ref_reg character varying, - edition character varying, - editeur character varying, - source character varying, - observation character varying, - thematique character varying, - date character varying, - duree_validite character varying, - date_fin character varying, - temporalite character varying, - type character varying, - row_hash text) - ON COMMIT DROP;""" - ) - ) - - columns_to_load = [ - "id", - "geom", - "entity_name", - "url", - "layer_name", - "facade", - "ref_reg", - "edition", - "editeur", - "source", - "observation", - "thematique", - "date", - "duree_validite", - "temporalite", - "type", - "row_hash" - ] - - logger.info("Loading to temporary table") - - - new_regulatory_areas[columns_to_load].to_sql( - "tmp_regulations_cacem", - connection, - if_exists="append", - index=False, - method=psql_insert_copy, - ) - - - logger.info("Updating regulations_cacem from temporary table {len(new_regulatory_areas)}") - connection.execute( - text( - """UPDATE regulations_cacem reg - SET geom = tmp.geom, - entity_name = tmp.entity_name, - url = tmp.url, - layer_name = tmp.layer_name, - facade = tmp.facade, - ref_reg = tmp.ref_reg, - edition = tmp.edition, - editeur = tmp.editeur, - source = tmp.source, - observation = tmp.observation, - thematique = tmp.thematique, - date = tmp.date, - duree_validite = tmp.duree_validite, - temporalite = tmp.temporalite, - type = tmp.type, - row_hash = tmp.row_hash - FROM tmp_regulations_cacem tmp - WHERE reg.id = tmp.id; - """ - ) - ) - - -@task(checkpoint=False) -def load_new_regulations(new_regulatory_areas: pd.DataFrame): - """Load the output of ``extract_new_regulations`` task into ``regulations_cacem`` - table. - - Args: - new_amp (pd.DataFrame): output of ``extract_new_regulations`` task. - """ - - load( - new_regulatory_areas, - table_name="regulations_cacem", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="append", - ) - -@task(checkpoint=False) -def extract_themes_regulatory_areas() -> pd.DataFrame: - return extract( - db_name="cacem_local", - query_filepath="cross/cacem/themes_regulatory_areas.sql", - ) - -@task(checkpoint=False) -def load_themes_regulatory_areas(themes_regulatory_areas: pd.DataFrame): - load( - themes_regulatory_areas, - table_name="themes_regulatory_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace" - ) - -@task(checkpoint=False) -def extract_tags_regulatory_areas() -> pd.DataFrame: - return extract( - db_name="cacem_local", - query_filepath="cross/cacem/tags_regulatory_areas.sql", - ) - -@task(checkpoint=False) -def load_tags_regulatory_areas(tags_regulatory_areas: pd.DataFrame): - load( - tags_regulatory_areas, - table_name="tags_regulatory_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace" - ) - - -with Flow("Regulations") as flow: - local_hashes = extract_local_hashes() - remote_hashes = extract_remote_hashes() - outer_hashes = merge_hashes(local_hashes, remote_hashes) - inner_merged = merge_hashes(local_hashes, remote_hashes, "inner") - - themes_regulatory_areas = extract_themes_regulatory_areas() - loaded_themes_regulatory_areas = load_themes_regulatory_areas(themes_regulatory_areas) - - tags_regulatory_areas = extract_tags_regulatory_areas() - loaded_tags_regulatory_areas = load_tags_regulatory_areas(tags_regulatory_areas) - - ids_to_delete = select_ids_to_delete(outer_hashes) - cond_delete = delete_required(ids_to_delete) - with case(cond_delete, True): - delete(ids_to_delete) - - ids_to_update = select_ids_to_update(inner_merged) - cond_update = update_required(ids_to_update) - with case(cond_update, True): - new_regulations = extract_new_regulatory_areas(ids_to_update) - update_regulatory_areas(new_regulations) - - ids_to_insert = select_ids_to_insert(outer_hashes) - cond_insert = insert_required(ids_to_insert) - with case(cond_insert, True): - new_regulations = extract_new_regulatory_areas(ids_to_insert) - load_new_regulations(new_regulations) - - themes_regulatory_areas = extract_themes_regulatory_areas() - load_themes_regulatory_areas(themes_regulatory_areas) - - tags_regulatory_areas = extract_tags_regulatory_areas() - load_tags_regulatory_areas(tags_regulatory_areas) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/regulatory_areas_open_data.py b/datascience/src/pipeline/flows/regulatory_areas_open_data.py deleted file mode 100644 index 855dd6944e..0000000000 --- a/datascience/src/pipeline/flows/regulatory_areas_open_data.py +++ /dev/null @@ -1,134 +0,0 @@ -from pathlib import Path - -import geopandas as gpd -import pandas as pd -from prefect import Flow, Parameter, case, task -from prefect.executors import LocalDaskExecutor - -from config import ( - IS_INTEGRATION, - REGULATORY_AREAS_CSV_RESOURCE_ID, - REGULATORY_AREAS_CSV_RESOURCE_TITLE, - REGULATORY_AREAS_DATASET_ID, - REGULATORY_AREAS_GEOPACKAGE_RESOURCE_ID, - REGULATORY_AREAS_GEOPACKAGE_RESOURCE_TITLE, -) -from src.pipeline.generic_tasks import extract -from src.pipeline.shared_tasks.control_flow import check_flow_not_running -from src.pipeline.shared_tasks.datagouv import ( - get_csv_file_object, - get_geopackage_file_object, - update_resource, -) - - -@task(checkpoint=False) -def extract_regulations_open_data() -> gpd.GeoDataFrame: - return extract( - "cacem_local", - "cross/cacem/regulations_open_data.sql", - backend="geopandas", - geom_col="geometry", - parse_dates=["edition", "date_fin", "date"] - ) - - -@task(checkpoint=False) -def get_regulations_for_csv(regulations: gpd.GeoDataFrame) -> pd.DataFrame: - - columns = [ - "id", - "ent_name", - "url", - "layer_name", - "facade", - "ref_reg", - "edition", - "source", - "obs", - "date", - "date_fin", - "validite", - "tempo", - "type", - "wkt", - "resume", - "poly_name", - "plan" - ] - - return pd.DataFrame(regulations[columns]) - - -@task(checkpoint=False) -def get_regulations_for_geopackage(regulations: gpd.GeoDataFrame) -> gpd.GeoDataFrame: - - columns = [ - "id", - "ent_name", - "url", - "layer_name", - "facade", - "ref_reg", - "edition", - "source", - "obs", - "date", - "date_fin", - "validite", - "tempo", - "type", - "resume", - "poly_name", - "plan", - "geometry", - ] - - return regulations[columns].copy(deep=True) - - -with Flow("Regulations open data", executor=LocalDaskExecutor()) as flow: - - flow_not_running = check_flow_not_running() - with case(flow_not_running, True): - - dataset_id = Parameter("dataset_id", default=REGULATORY_AREAS_DATASET_ID) - csv_resource_id = Parameter( - "csv_resource_id", default=REGULATORY_AREAS_CSV_RESOURCE_ID - ) - gpkg_resource_id = Parameter( - "gpkg_resource_id", default=REGULATORY_AREAS_GEOPACKAGE_RESOURCE_ID - ) - csv_resource_title = Parameter( - "csv_resource_title", default=REGULATORY_AREAS_CSV_RESOURCE_TITLE - ) - gpkg_resource_title = Parameter( - "gpkg_resource_title", default=REGULATORY_AREAS_GEOPACKAGE_RESOURCE_TITLE - ) - is_integration = Parameter("is_integration", default=IS_INTEGRATION) - - regulations = extract_regulations_open_data() - - regulations_for_csv = get_regulations_for_csv(regulations) - regulations_for_geopackage = get_regulations_for_geopackage(regulations) - - csv_file = get_csv_file_object(regulations_for_csv) - geopackage_file = get_geopackage_file_object(regulations_for_geopackage, layers="facade") - - update_resource( - dataset_id=dataset_id, - resource_id=csv_resource_id, - resource_title=csv_resource_title, - resource=csv_file, - mock_update=is_integration, - ) - - update_resource( - dataset_id=dataset_id, - resource_id=gpkg_resource_id, - resource_title=gpkg_resource_title, - resource=geopackage_file, - mock_update=is_integration, - ) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/remove_broken_missions_resources_links.py b/datascience/src/pipeline/flows/remove_broken_missions_resources_links.py deleted file mode 100644 index f4b878b218..0000000000 --- a/datascience/src/pipeline/flows/remove_broken_missions_resources_links.py +++ /dev/null @@ -1,14 +0,0 @@ -from pathlib import Path - -from prefect import Flow - -from config import QUERIES_LOCATION -from src.pipeline.shared_tasks.etl import run_sql_script - -with Flow("Remove broken missions resources links") as flow: - run_sql_script( - QUERIES_LOCATION - / "monitorenv/remove_broken_missions_resources_links.sql" - ) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/semaphores.py b/datascience/src/pipeline/flows/semaphores.py deleted file mode 100644 index cc5033fb7b..0000000000 --- a/datascience/src/pipeline/flows/semaphores.py +++ /dev/null @@ -1,57 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import Flow, task -from sqlalchemy import DDL - -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_semaphores() -> pd.DataFrame: - """ - Extract Semaphores from the cacem_local database as a DataFrame. - - Returns: - pd.DataFrame: GeoDataFrame of Semaphores - """ - - return extract(db_name="cacem_local", query_filepath="cross/cacem/semaphore.sql") - - -@task(checkpoint=False) -def load_semaphores(semaphores: pd.DataFrame): - - logger = prefect.context.get("logger") - - load( - semaphores, - table_name="semaphores", - schema="public", - db_name="monitorenv_remote", - logger=logger, - how="upsert", - df_id_column="id", - table_id_column="id", - init_ddls=[ - DDL( - "ALTER TABLE public.reportings_source " - "DROP CONSTRAINT fk_semaphores;" - ) - ], - end_ddls=[ - DDL( - "ALTER TABLE public.reportings_source " - "ADD CONSTRAINT fk_semaphores " - "FOREIGN KEY (semaphore_id) REFERENCES public.semaphores (id);" - ) - ], - ) - - -with Flow("Semaphores") as flow: - semaphores = extract_semaphores() - load_semaphores(semaphores) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/themes_and_tags.py b/datascience/src/pipeline/flows/themes_and_tags.py deleted file mode 100644 index b7bc7cfb14..0000000000 --- a/datascience/src/pipeline/flows/themes_and_tags.py +++ /dev/null @@ -1,308 +0,0 @@ -import pandas as pd -import prefect -from pathlib import Path -from prefect import Flow, case, task -from sqlalchemy import text -from src.db_config import create_engine -from src.pipeline.generic_tasks import extract, load -from src.pipeline.shared_tasks.update_queries import insert_required, merge_hashes, select_ids_to_insert, select_ids_to_update, update_required -from src.pipeline.utils import psql_insert_copy -from src.read_query import read_query - - -""" Thèmes """ -@task(checkpoint=False) -def extract_local_themes() -> pd.DataFrame: - """ - Extract themes from cacem - - Returns: - pd.DataFrame: GeoDataFrame of themes + row_hash - """ - return extract( - db_name="cacem_local", query_filepath="cross/cacem/themes_hashes.sql" - ) - - -@task(checkpoint=False) -def extract_remote_themes() -> pd.DataFrame: - """ - Extract themes from monitorenv - - Returns: - pd.DataFrame: GeoDataFrame of themes id + row_hash - """ - return extract( - db_name="monitorenv_remote", - query_filepath="monitorenv/themes_hashes.sql", - ) - -@task(checkpoint=False) -def extract_new_themes(ids_to_update: set) -> pd.DataFrame: - return extract( - "cacem_local", - "cross/cacem/themes.sql", - params={"ids": tuple(ids_to_update)}, - ) - -@task(checkpoint=False) -def update_themes(new_themes: pd.DataFrame): - """Load the output of ``extract_rows_to_update`` task into ``themes`` - table. - - Args: - new_themes (pd.DataFrame): output of ``extract_rows_to_update`` task. - """ - e = create_engine("monitorenv_remote") - logger = prefect.context.get("logger") - - with e.begin() as connection: - logger.info("Creating temporary table") - connection.execute( - text( - """CREATE TEMP TABLE tmp_themes( - id serial, - name varchar, - parent_id int, - started_at timestamp, - ended_at timestamp, - control_plan_themes_id int, - control_plan_sub_themes_id int, - control_plan_tags_id int, - reportings_control_plan_sub_themes_id int) - ON COMMIT DROP;""" - ) - ) - - columns_to_load = [ - "id", - "name", - "parent_id", - "started_at", - "ended_at", - "control_plan_themes_id", - "control_plan_sub_themes_id", - "control_plan_tags_id", - "reportings_control_plan_sub_themes_id" - ] - - int_columns = [ - "id", - "parent_id", - "control_plan_themes_id", - "control_plan_sub_themes_id", - "control_plan_tags_id", - "reportings_control_plan_sub_themes_id" - ] - for col in int_columns: - new_themes[col] = new_themes[col].astype("Int64") - - - logger.info("Loading to temporary table") - - new_themes[columns_to_load].to_sql( - "tmp_themes", - connection, - if_exists="append", - index=False, - method=psql_insert_copy, - - ) - - logger.info("Updating themes from temporary table {len(new_themes)}") - connection.execute( - text( - """UPDATE themes - SET name = tmp.name, - parent_id = tmp.parent_id, - started_at = tmp.started_at, - ended_at = tmp.ended_at, - control_plan_themes_id = tmp.control_plan_themes_id, - control_plan_sub_themes_id = tmp.control_plan_sub_themes_id, - control_plan_tags_id = tmp.control_plan_tags_id, - reportings_control_plan_sub_themes_id = tmp.reportings_control_plan_sub_themes_id - FROM tmp_themes tmp - where themes.id = tmp.id; - """ - ) - ) - - -@task(checkpoint=False) -def load_new_themes(new_themes: pd.DataFrame): - """Load the output of ``extract_new_themes`` task into ``themes`` - table. - - Args: - new_themes (pd.DataFrame): output of ``extract_new_themes`` task. - """ - load( - new_themes, - table_name="themes", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="append", - nullable_integer_columns=["parent_id", "control_plan_themes_id", "control_plan_sub_themes_id", "control_plan_tags_id", "reportings_control_plan_sub_themes_id"] - ) - - -""" Tags """ -@task(checkpoint=False) -def extract_local_tags() -> pd.DataFrame: - """ - Extract tags from cacem - - Returns: - pd.DataFrame: GeoDataFrame of tags + row_hash - """ - return extract( - db_name="cacem_local", query_filepath="cross/cacem/tags_hashes.sql" - ) - - -@task(checkpoint=False) -def extract_remote_tags() -> pd.DataFrame: - """ - Extract tags from monitorenv - - Returns: - pd.DataFrame: GeoDataFrame of tags - """ - return extract( - db_name="monitorenv_remote", - query_filepath="monitorenv/tags_hashes.sql", - ) - -@task(checkpoint=False) -def extract_new_tags(ids_to_update: set) -> pd.DataFrame: - return extract( - "cacem_local", - "cross/cacem/tags.sql", - params={"ids": tuple(ids_to_update)}, - ) - -@task(checkpoint=False) -def update_tags(new_tags: pd.DataFrame): - """Load the output of ``extract_rows_to_update`` task into ``tags`` - table. - - Args: - new_tags (pd.DataFrame): output of ``extract_rows_to_update`` task. - """ - e = create_engine("monitorenv_remote") - logger = prefect.context.get("logger") - - with e.begin() as connection: - logger.info("Creating temporary table") - connection.execute( - text( - """CREATE TEMP TABLE tmp_tags( - id serial, - name varchar, - parent_id int, - started_at timestamp, - ended_at timestamp) - ON COMMIT DROP;""" - ) - ) - - columns_to_load = [ - "id", - "name", - "parent_id", - "started_at", - "ended_at", - ] - - int_columns = [ - "id", - "parent_id", - ] - for col in int_columns: - new_tags[col] = new_tags[col].astype("Int64") - - logger.info("Loading to temporary table") - - new_tags[columns_to_load].to_sql( - "tmp_tags", - connection, - if_exists="append", - index=False, - method=psql_insert_copy, - ) - - logger.info(f"Updating tags from temporary table {len(new_tags)}") - connection.execute( - text( - """UPDATE tags - SET name = tmp.name, - parent_id = tmp.parent_id, - started_at = tmp.started_at, - ended_at = tmp.ended_at - FROM tmp_tags tmp - where tags.id = tmp.id; - """ - ) - ) - - -@task(checkpoint=False) -def load_new_tags(new_tags: pd.DataFrame): - """Load the output of ``extract_new_tags`` task into ``tags`` - table. - - Args: - new_tags (pd.DataFrame): output of ``extract_new_tags`` task. - """ - load( - new_tags, - table_name="tags", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="append", - nullable_integer_columns=["parent_id"] - ) - - -with Flow("Themes and Tags") as flow: - """ Thèmes """ - local_themes = extract_local_themes() - remote_themes = extract_remote_themes() - outer = merge_hashes(local_themes, remote_themes) - inner_merged = merge_hashes(local_themes, remote_themes, "inner") - - themes_ids_to_update = select_ids_to_update(inner_merged) - cond_update = update_required(themes_ids_to_update) - with case(cond_update, True): - new_themes = extract_new_themes(themes_ids_to_update) - update_themes(new_themes) - - themes_ids_to_insert = select_ids_to_insert(outer) - cond_insert = insert_required(themes_ids_to_insert) - with case(cond_insert, True): - new_themes = extract_new_themes(themes_ids_to_insert) - load_new_themes(new_themes) - - - """ Tags """ - local_tags = extract_local_tags() - remote_tags = extract_remote_tags() - outer = merge_hashes(local_tags, remote_tags) - inner_merged = merge_hashes(local_tags, remote_tags, "inner") - - tags_ids_to_update = select_ids_to_update(inner_merged) - cond_update = update_required(tags_ids_to_update) - with case(cond_update, True): - new_tags = extract_new_tags(tags_ids_to_update) - update_tags(new_tags) - - tags_ids_to_insert = select_ids_to_insert(outer) - cond_insert = insert_required(tags_ids_to_insert) - with case(cond_insert, True): - new_tags = extract_new_tags(tags_ids_to_insert) - load_new_tags(new_tags) - - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/three_hundred_meters_areas.py b/datascience/src/pipeline/flows/three_hundred_meters_areas.py deleted file mode 100644 index 3bd3073a4d..0000000000 --- a/datascience/src/pipeline/flows/three_hundred_meters_areas.py +++ /dev/null @@ -1,35 +0,0 @@ -import geopandas as gpd -import prefect -from pathlib import Path -from prefect import Flow, task -from src.pipeline.generic_tasks import extract, load - - -@task(checkpoint=False) -def extract_three_hundred_meters_areas() -> gpd.GeoDataFrame: - return extract( - db_name="cacem_local", - query_filepath="cross/cacem/three_hundred_meters_areas.sql", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - - -@task(checkpoint=False) -def load_three_hundred_meters_areas(three_hundred_meters_areas: gpd.GeoDataFrame): - load( - three_hundred_meters_areas, - table_name="three_hundred_meters_areas", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="replace", - ) - - -with Flow("300 meters areas") as flow: - three_hundred_meters_areas = extract_three_hundred_meters_areas() - load_three_hundred_meters_areas(three_hundred_meters_areas) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/update_departments_and_facades.py b/datascience/src/pipeline/flows/update_departments_and_facades.py deleted file mode 100644 index e2405f3f8c..0000000000 --- a/datascience/src/pipeline/flows/update_departments_and_facades.py +++ /dev/null @@ -1,28 +0,0 @@ -from pathlib import Path - -from prefect import Flow, case -from prefect.executors import LocalDaskExecutor - -from config import QUERIES_LOCATION -from src.pipeline.shared_tasks.control_flow import check_flow_not_running -from src.pipeline.shared_tasks.etl import run_sql_script - -with Flow( - "Update departments and façades for missions and envActions", - executor=LocalDaskExecutor(), -) as flow: - - flow_not_running = check_flow_not_running() - with case(flow_not_running, True): - - run_sql_script( - QUERIES_LOCATION / "monitorenv/update_missions_facades.sql" - ) - run_sql_script( - QUERIES_LOCATION / "monitorenv/update_actions_departments.sql" - ) - run_sql_script( - QUERIES_LOCATION / "monitorenv/update_actions_facades.sql" - ) - -flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows/vessel_repository.py b/datascience/src/pipeline/flows/vessel_repository.py deleted file mode 100644 index b3bf8644d0..0000000000 --- a/datascience/src/pipeline/flows/vessel_repository.py +++ /dev/null @@ -1,202 +0,0 @@ -from copy import deepcopy -from pathlib import Path -from typing import List -from lxml import etree -import pandas as pd -from src.db_config import create_engine -from config import LIBRARY_LOCATION -from prefect import Flow, task, context, case -from src.pipeline.generic_tasks import load -from src.pipeline.helpers.strings import to_snake_case -from src.pipeline.utils import remove_file - -TAGS_TO_INCLUDE = { -"ShipId", -"Status", -"Category", -"IsBanned", -"IMONumber", -"MMSINumber", -"CallSign", -"Immatriculation", -"ShipName", -"Flag", -"PortOfRegistry", -"ProfessionalType", -"LeisureType", -"CommercialName", -"Length", -"BatchId", -"RowNumber", -} -OWNER_TAGS_TO_INCLUDE = { -"DateOfInformation", -"LastName", -"FirstName", -"DateOfBirth", -"PostalAddress", -"Phone", -"Email", -"CompanyName", -"Nationality", -"BusinessSegment", -"LegalStatus", -"StartDate" -} - - -@task(checkpoint=False) -def get_xsd_file(directory: str) -> Path: - directory = Path(directory) - if not directory.exists(): - raise FileNotFoundError(f"Le dossier {directory} n'existe pas") - - xsd_files = list(directory.glob("*.xsd")) - - if len(xsd_files) == 0: - raise FileNotFoundError("Aucun fichier XSD trouvé dans le répertoire") - - xsd_file = xsd_files[0] - return xsd_file - - -@task(checkpoint=False) -def get_xml_files(directory: str) -> List[Path]: - directory = Path(directory) - if not directory.exists(): - raise FileNotFoundError(f"Le dossier {directory} n'existe pas") - - xml_files = list(directory.glob("*.xml")) - logger = context.get("logger") - logger.info(f"{len(xml_files)} fichiers XML trouvés") - return xml_files - - -@task(checkpoint=False) -def get_xsd_schema(xsd_file_path: str): - xsd_path = Path(xsd_file_path) - if not xsd_path.exists(): - raise FileNotFoundError(f"Fichier XSD non trouvé : {xsd_file_path}") - - xsd_doc = etree.parse(str(xsd_path)) - schema = etree.XMLSchema(xsd_doc) - return schema - -def parse_xml_and_load(xml_file_path: str, schema=None, batch_size: int = 100000, connection = None): - xml_path = Path(xml_file_path) - if not xml_path.exists(): - raise FileNotFoundError(f"Fichier XML non trouvé : {xml_file_path}") - logger = context.get("logger") - header_elem = None # variable pour stocker le header - context_iter = etree.iterparse(str(xml_path), events=("end",), tag=("Header","ShipDescription"), recover=True) - - batch = [] - for event, elem in context_iter: - # Capture le header la première fois - if header_elem is None and elem.tag == "Header": - header_elem = elem - continue - - fake_root = etree.Element("racine") - fake_header = header_elem - fake_body = etree.Element("Body") - fake_root.append(fake_header) - fake_root.append(fake_body) - fake_body.append(elem) - # Validation du sous-arbre - if schema is not None and not schema.validate(fake_root): - log = schema.error_log.last_error - logger.warning(f"XML invalide ligne {log.line} : {log.message}") - elem.clear() - continue - - record = {} - parse_xml_nodes(elem, record) - batch.append(record) - - # --- Envoi d’un batch complet - if len(batch) >= batch_size: - load_vessels_batch(pd.DataFrame(batch), connection) - batch.clear() - - # --- Libération mémoire - elem.clear() - while elem.getprevious() is not None: - del elem.getparent()[0] - - # --- Dernier batch partiel - if batch: - load_vessels_batch(pd.DataFrame(batch), connection) - logger.info(f"File parsed : {xml_file_path}") - -def parse_xml_nodes(elem, record): - for child in elem: - if child.tag in TAGS_TO_INCLUDE: - record[f"{to_snake_case(child.tag)}"] = child.text - - metadata = elem.find(".//Metadata") - if metadata is not None: - for child in metadata: - if child.tag in TAGS_TO_INCLUDE: - record[f"{to_snake_case(child.tag)}"] = child.text - - identification = elem.find(".//Identification") - if identification is not None: - for child in identification: - if child.tag in TAGS_TO_INCLUDE: - record[f"{to_snake_case(child.tag)}"] = child.text - - characteristics = identification.find(".//Characteristics") - if characteristics is not None: - for child in characteristics: - if child.tag in TAGS_TO_INCLUDE: - record[f"{to_snake_case(child.tag)}"] = child.text - - owner = characteristics.find(".//Owner") - if owner is not None: - for child in owner: - if child.tag in OWNER_TAGS_TO_INCLUDE: - record[f"owner_{to_snake_case(child.tag)}"] = child.text - -def load_vessels_batch(vessels, connection): - logger = context.get("logger") - load( - vessels, - table_name="vessels", - schema="public", - db_name="monitorenv_remote", - logger=logger, - how="append", - connection=connection - ) - -@task(checkpoint=False) -def parse_all_xml_files(xml_files, xsd_schema, batch_size=100000): - engine = create_engine("monitorenv_remote") - try: - with engine.begin() as connection: - for xml_file in xml_files: - logger = context.get("logger") - logger.info(f"Parsing file {xml_file}") - parse_xml_and_load(xml_file, xsd_schema, batch_size, connection) - return True - - except Exception as e: - logger.error(f"Error: {e}, rollback") - raise - - -@task(checkpoint=False) -def delete_files(xml_files: List[Path]): - for xml_file in xml_files: - remove_file(xml_file, ignore_errors=False) - -with Flow("Vessel repository") as flow: - xsd_file = get_xsd_file(LIBRARY_LOCATION / f"pipeline/data/") - xsd_schema = get_xsd_schema(xsd_file) - xml_files = get_xml_files(LIBRARY_LOCATION / f"pipeline/data/") - xml_parsed = parse_all_xml_files(xml_files, xsd_schema) - with case(xml_parsed, True): - delete_files(xml_files) - -flow.file_name = Path(__file__).name \ No newline at end of file diff --git a/datascience/src/pipeline/flows_config.py b/datascience/src/pipeline/flows_config.py deleted file mode 100644 index f0c90077e4..0000000000 --- a/datascience/src/pipeline/flows_config.py +++ /dev/null @@ -1,166 +0,0 @@ -from docker.types import Mount -from dotenv import dotenv_values -from prefect.executors.dask import LocalDaskExecutor -from prefect.run_configs.docker import DockerRun -from prefect.schedules import CronSchedule, Schedule, clocks -from prefect.storage.local import Local - -from config import ( - DOCKER_IMAGE, - EMAIL_ALL_UNITS, - FLOWS_LOCATION, - IS_INTEGRATION, - MONITORENV_VERSION, - ROOT_DIRECTORY, - TEST_MODE, - VESSEL_FILES_DIRECTORY, - VESSEL_FILES_GID, -) -from src.pipeline.flows import ( - admin_areas, - amp_cacem, - amp_ofb, - beaches, - control_objectives, - competence_cross_areas, - email_actions_to_units, - facade_areas, - facade_areas_unextended, - fao_areas, - historic_control_units, - historic_controls, - infractions, - localized_areas, - marpol, - refresh_materialized_view, - regulations, - remove_broken_missions_resources_links, - semaphores, - themes_and_tags, - three_hundred_meters_areas, - update_departments_and_facades, - regulatory_areas_open_data, - vessel_repository -) - -################################ Define flow schedules ################################ -amp_ofb.flow.schedule = CronSchedule("2 0 * * *") - -amp_cacem.flow.schedule = CronSchedule("22 0 * * *") - -email_actions_to_units.flow.schedule = Schedule( - clocks=[ - clocks.CronClock( - "0 5 * * 1", - parameter_defaults={ - "start_days_ago": 7, - "end_days_ago": 1, - "test_mode": TEST_MODE, - "is_integration": IS_INTEGRATION, - "email_all_units": EMAIL_ALL_UNITS, - }, - ), - ] -) - -infractions.flow.schedule = CronSchedule("2 8,14 * * *") - -refresh_materialized_view.flow.schedule = Schedule( - clocks=[ - clocks.CronClock( - "30 * * * *", - parameter_defaults={ - "view_name": "analytics_actions", - }, - ), - clocks.CronClock( - "35 12 * * *", - parameter_defaults={ - "view_name": "analytics_surveillance_density_map", - }, - ), - ] -) -themes_and_tags.flow.schedule = CronSchedule("2,12,22,32,42,52 * * * *") - -regulations.flow.schedule = CronSchedule("6,16,26,36,46,56 * * * *") - -semaphores.flow.schedule = CronSchedule("3 5,15 * * *") - -regulatory_areas_open_data.flow.schedule = CronSchedule("0 20 * * 5") - - -###################### List flows to register with prefect server ##################### -flows_to_register = [ - admin_areas.flow, - amp_cacem.flow, - amp_ofb.flow, - beaches.flow, - competence_cross_areas.flow, - control_objectives.flow, - email_actions_to_units.flow, - facade_areas.flow, - facade_areas_unextended.flow, - fao_areas.flow, - historic_controls.flow, - historic_control_units.flow, - infractions.flow, - localized_areas.flow, - marpol.flow, - refresh_materialized_view.flow, - regulations.flow, - remove_broken_missions_resources_links.flow, - semaphores.flow, - themes_and_tags.flow, - three_hundred_meters_areas.flow, - update_departments_and_facades.flow, - regulatory_areas_open_data.flow, - vessel_repository.flow -] - -################################ Define flows' executor ############################### -for flow in flows_to_register: - flow.executor = LocalDaskExecutor() - -################################ Define flows' storage ################################ -# This defines where the executor can find the flow.py file for each flow **inside** -# the container. -for flow in flows_to_register: - flow.storage = Local( - add_default_labels=False, - stored_as_script=True, - path=(FLOWS_LOCATION / flow.file_name).as_posix(), - ) - -################### Define flows' run config #################### -for flow in flows_to_register: - host_config = None - - if flow.name in ("Control objectives",): - host_config = { - "mounts": [ - Mount( - target="/home/monitorenv-pipeline/datascience/src/pipeline/data", - source="/opt/data", - type="bind", - ) - ], - } - if flow.name in ("Vessel repository",): - host_config = { - "group_add": [VESSEL_FILES_GID], - "mounts": [ - Mount( - target="/home/monitorenv-pipeline/datascience/src/pipeline/data", - source=VESSEL_FILES_DIRECTORY, - type="bind", - ) - ], - } - - flow.run_config = DockerRun( - image=f"{DOCKER_IMAGE}:{MONITORENV_VERSION}", - host_config=host_config, - env=dotenv_values(ROOT_DIRECTORY / ".env"), - labels=["monitorenv"], - ) diff --git a/datascience/src/pipeline/generic_tasks.py b/datascience/src/pipeline/generic_tasks.py deleted file mode 100644 index 4c2b42fc1a..0000000000 --- a/datascience/src/pipeline/generic_tasks.py +++ /dev/null @@ -1,313 +0,0 @@ -import logging -from pathlib import Path -from typing import List, Union - -import geopandas as gpd -import pandas as pd -from sqlalchemy import DDL -from sqlalchemy.engine import Connection - -from src.db_config import create_engine -from src.pipeline import utils -from src.pipeline.processing import prepare_df_for_loading -from src.pipeline.utils import get_table, psql_insert_copy -from src.read_query import read_saved_query - - -def extract( - db_name: str, - query_filepath: Union[Path, str], - dtypes: Union[None, dict] = None, - parse_dates: Union[list, dict, None] = None, - params=None, - backend: str = "pandas", - geom_col: str = "geom", - crs: Union[int, None] = None, -) -> Union[pd.DataFrame, gpd.GeoDataFrame]: - """Run SQL query against the indicated database and return the result as a - `pandas.DataFrame`. - - Args: - db_name (str): name of the database to extract from : "fmc", "ocan", - "monitorfish_local" or "monitorenv_remote" - query_filepath (Union[Path, str]): path to .sql file, starting from the saved - queries folder. example : "ocan/nav_fr_peche.sql" - dtypes (Union[None, dict], optional): If specified, use {col: dtype, …}, where - col is a column label and dtype is a numpy.dtype or Python type to cast - one or more of the DataFrame’s columns to column-specific types. - Defaults to None. - parse_dates (Union[list, dict, None], optional): - - List of column names to parse as dates. - - Dict of ``{column_name: format string}`` where format string is - strftime compatible in case of parsing string times or is one of - (D, s, ns, ms, us) in case of parsing integer timestamps. - - Dict of ``{column_name: arg dict}``, where the arg dict corresponds - to the keyword arguments of :func:`pandas.to_datetime` - - Defaults to None. - params (Union[dict, None], optional): Parameters to pass to execute method. - Defaults to None. - backend (str, optional) : 'pandas' to run a SQL query and return a - `pandas.DataFrame` or 'geopandas' to run a PostGIS query and return a - `geopandas.GeoDataFrame`. Defaults to 'pandas'. - geom_col (str, optional): column name to convert to shapely geometries when - `backend` is 'geopandas'. Ignored when `backend` is 'pandas'. Defaults to - 'geom'. - crs (Union[None, str], optional) : CRS to use for the returned GeoDataFrame; - if not set, tries to determine CRS from the SRID associated with the first - geometry in the database, and assigns that to all geometries. Ignored when - `backend` is 'pandas'. Defaults to None. - - Returns: - Union[pd.DataFrame, gpd.GeoDataFrame]: Query results - """ - - res = read_saved_query( - db_name, - query_filepath, - parse_dates=parse_dates, - params=params, - backend=backend, - geom_col=geom_col, - crs=crs, - ) - - if dtypes: - res = res.astype(dtypes) - - return res - - -def load( - df: Union[pd.DataFrame, gpd.GeoDataFrame], - *, - table_name: str, - schema: str, - logger: logging.Logger, - how: str = "replace", - db_name: str = None, - pg_array_columns: list = None, - handle_array_conversion_errors: bool = True, - value_on_array_conversion_error: str = "{}", - jsonb_columns: list = None, - table_id_column: str = None, - df_id_column: str = None, - nullable_integer_columns: list = None, - timedelta_columns: list = None, - connection: Connection = None, - init_ddls: List[DDL] = None, - end_ddls: List[DDL] = None, -): - """ - Load a DataFrame or GeoDataFrame to a database table using sqlalchemy. The table - must already exist in the database. - - Args: - df (Union[pd.DataFrame, gpd.GeoDataFrame]): data to load - table_name (str): name of the table - schema (str): database schema of the table - logger (logging.Logger): logger instance - how (str): one of - - 'replace' to delete all rows in the table before loading - - 'append' to append the data to rows already in the table - - 'upsert' to append the rows to the table, replacing the rows whose id is - already - db_name (str, optional): Required if a `connection` is not provided. - 'monitorenv_remote' or 'monitorfish_local'. Defaults to None. - pg_array_columns (list, optional): columns containing sequences that must be - serialized before loading into columns with Postgresql `Array` type - handle_array_conversion_errors (bool): whether to handle or raise upon error - during the serialization of columns to load into Postgresql `Array` columns. - Defaults to True. - value_on_array_conversion_error (str, optional): if - `handle_array_conversion_errors`, the value to use when an error must be - handled. Defaults to '{}'. - jsonb_columns (list, optional): columns containing values that must be - serialized before loading into columns with Postgresql `JSONB` type - table_id_column (str, optional): name of the table column to use an id. - Required if `how` is "upsert". - df_id_column (str, optional): name of the DataFrame column to use an id. - Required if `how` is "upsert". - nullable_integer_columns (list, optional): columns containing values - that must loaded into columns with Postgresql `Integer` type. If these - columns contain `NA` values, pandas will automatically change the dtype to - `float` and the loading into Postgreql `Integer` columns will fail, so it is - necessary to serialize these values as `Integer`-compatible `str` objects. - timedelta_columns (list, optional): columns containing `Timedelta` values to - load into Postgresql `Interval` columns. If these columns contain `NaT` - values, the loading will fail, so it is necessary to serialize these values - as `Interval`-compatible `str` objects. - connection (Connection, optional): Databse connection to use for the insert - operation. If not provided, `db_name` must be given and a connection to the - designated database will be created for the insert operation. - Defaults to None. - init_ddls: (List[DDL], optional): If given, these DDLs will be executed before - the loading operation. Defaults to None. - end_ddls: (List[DDL], optional): If given, these DDLs will be executed after - the loading operation. Defaults to None. - """ - - df = prepare_df_for_loading( - df, - logger, - pg_array_columns=pg_array_columns, - handle_array_conversion_errors=handle_array_conversion_errors, - value_on_array_conversion_error=value_on_array_conversion_error, - jsonb_columns=jsonb_columns, - nullable_integer_columns=nullable_integer_columns, - timedelta_columns=timedelta_columns, - ) - - if connection is None: - e = create_engine(db_name) - with e.begin() as connection: - load_with_connection( - df=df, - connection=connection, - table_name=table_name, - schema=schema, - logger=logger, - how=how, - table_id_column=table_id_column, - df_id_column=df_id_column, - init_ddls=init_ddls, - end_ddls=end_ddls, - ) - else: - load_with_connection( - df=df, - connection=connection, - table_name=table_name, - schema=schema, - logger=logger, - how=how, - table_id_column=table_id_column, - df_id_column=df_id_column, - init_ddls=init_ddls, - end_ddls=end_ddls, - ) - - -def load_with_connection( - df: Union[pd.DataFrame, gpd.GeoDataFrame], - *, - connection: Connection, - table_name: str, - schema: str, - logger: logging.Logger, - how: str = "replace", - table_id_column: Union[None, str] = None, - df_id_column: Union[None, str] = None, - init_ddls: List[DDL] = None, - end_ddls: List[DDL] = None, -): - if init_ddls: - for ddl in init_ddls: - connection.execute(ddl) - - table = get_table(table_name, schema, connection, logger) - if how == "replace": - # Delete all rows from table - utils.delete(table, connection, logger) - - elif how == "upsert": - # Delete rows that are in the DataFrame from the table - - try: - assert df_id_column is not None - except AssertionError: - raise ValueError("df_id_column cannot be null if how='upsert'") - try: - assert table_id_column is not None - except AssertionError: - raise ValueError("table_id_column cannot be null if how='upsert'") - - ids_to_delete = set(df[df_id_column].unique()) - - utils.delete_rows( - table=table, - id_column=table_id_column, - ids_to_delete=ids_to_delete, - connection=connection, - logger=logger, - ) - - elif how == "append": - # Nothing to do - pass - - else: - raise ValueError( - f"how must be 'replace', 'upsert' or 'append', got {how}" - ) - - # Insert data into table - logger.info(f"Loading into {schema}.{table_name}") - - if isinstance(df, gpd.GeoDataFrame): - logger.info("GeodateFrame detected, using to_postgis") - df.to_postgis( - name=table_name, - con=connection, - schema=schema, - index=False, - if_exists="append", - ) - - elif isinstance(df, pd.DataFrame): - df.to_sql( - name=table_name, - con=connection, - schema=schema, - index=False, - method=psql_insert_copy, - if_exists="append", - ) - - else: - raise ValueError("df must be DataFrame or GeoDataFrame.") - - if end_ddls: - for ddl in end_ddls: - connection.execute(ddl) - - -def delete_rows( - *, - table_name: str, - schema: str, - db_name: str, - table_id_column: str, - ids_to_delete: set, - logger: logging.Logger, -): - """ - Delete rows from a database table. - - Args: - table_name (str): name of the table - schema (str): database schema of the table - db_name (str): name of the database. One of - - 'monitorenv_remote' - - 'monitorfish_local' - table_id_column (str): name of the id column in the database. - ids_to_delete (set): the ids of the rows to delete. - logger (logging.Logger): logger instance. - """ - - e = create_engine(db_name) - table = get_table(table_name, schema, e, logger) - - with e.begin() as connection: - n_rows = len(ids_to_delete) - if n_rows == 0: - logger.info("No rows to delete, skipping.") - - else: - utils.delete_rows( - table=table, - id_column=table_id_column, - ids_to_delete=ids_to_delete, - connection=connection, - logger=logger, - ) diff --git a/datascience/src/pipeline/helpers/__init__.py b/datascience/src/pipeline/helpers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/pipeline/helpers/dates.py b/datascience/src/pipeline/helpers/dates.py deleted file mode 100644 index b159d87b2f..0000000000 --- a/datascience/src/pipeline/helpers/dates.py +++ /dev/null @@ -1,127 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime, timedelta -from typing import List, Union - -import pandas as pd - - -@dataclass -class Period: - start: datetime - end: datetime - - -def make_periods( - start_datetime_utc: datetime, - end_datetime_utc: datetime, - period_duration: timedelta, - overlap: Union[None, timedelta] = None, -) -> List[Period]: - """ - Returns a list of `Period`s of duration `period_duration` covering the time range - from `start_datetime_utc` to `end_datetime_utc`. - - If `overlap` is specified, the `Period`s returned will overlap by the amount - specified, otherwise the end of one period will coincide with the start of the - next one. - - If `period_duration` is shorter than the time between `start_datetime_utc` and - `end_datetime_utc`, returns a list with a single `Period` starting on - `start_datetime_utc` and ending on `end_datetime_utc`. - - This is useful to break a long time range into smaller periods for processing time - series data that would take up too much memory to handle in one piece. - - Args: - start_datetime_utc (datetime): start of the period to cover - end_datetime_utc (datetime): end of the period to cover - period_duration (timedelta): duration of the individual - periods returned - overlap (Union[None, timedelta]): overlap between successive - periods, if specified. Defaults to `None`. - """ - - if not overlap: - overlap = timedelta(0) - - try: - assert period_duration > overlap - except AssertionError: - raise ValueError("'period_duration' cannot be shorter than 'overlap'.") - - try: - assert end_datetime_utc >= start_datetime_utc - except AssertionError: - raise ValueError( - "'end_datetime_utc' cannot be before than 'start_datetime_utc'." - ) - - if end_datetime_utc - start_datetime_utc <= period_duration: - return [Period(start=start_datetime_utc, end=end_datetime_utc)] - else: - periods = make_periods( - start_datetime_utc=start_datetime_utc + period_duration - overlap, - end_datetime_utc=end_datetime_utc, - period_duration=period_duration, - overlap=overlap, - ) - - periods.insert( - 0, - Period( - start=start_datetime_utc, - end=start_datetime_utc + period_duration, - ), - ) - return periods - - -def get_datetime_intervals( - s: pd.Series, unit: str = None, how: str = "backward" -) -> pd.Series: - """Takes a pandas Series with datetime dtype. - Return a pandas Series with the same index and with time intervals between the - successives values of the input Series as values. - - Args: - s (Series): pandas Series with datetime dtype - unit (Union[str, None]): - - if None, returns values as pandas Timedelta - - if provided, must be one of 's', 'min' or 'h', in which case values - are returned as a float. - Defaults to None. - how (str): if, 'forward', computes the interval between each position and the - next one. If 'backward', computes the interval between each position and - the previous one. - Defaults to 'backward' - - Returns: - pd.Series: Series of time intervals between the values of the input Series - """ - - if how == "backward": - shift = 1 - elif how == "forward": - shift = -1 - else: - raise ValueError(f"how expects 'backward' or 'forward', got '{how}'") - - intervals = pd.Series( - index=s.index, - data=shift * (s.values - s.shift(shift).values), - ) - - if unit: - intervals = intervals.map(lambda dt: dt.total_seconds()) - if unit == "h": - intervals = intervals / 3600 - elif unit == "min": - intervals = intervals / 60 - elif unit == "s": - pass - else: - raise ValueError( - f"unit must be None, 'h', 'min' or 's', got '{unit}'." - ) - - return intervals diff --git a/datascience/src/pipeline/helpers/emails.py b/datascience/src/pipeline/helpers/emails.py deleted file mode 100644 index ed5461eabf..0000000000 --- a/datascience/src/pipeline/helpers/emails.py +++ /dev/null @@ -1,152 +0,0 @@ -import mimetypes -import smtplib -from email.message import EmailMessage -from mimetypes import guess_type -from pathlib import Path -from typing import List, Union - -from config import ( - EMAIL_SERVER_PORT, - EMAIL_SERVER_URL, - MONITORENV_SENDER_EMAIL_ADDRESS, -) - - -def create_html_email( - to: Union[str, List[str]], - subject: str, - html: str, - from_: str = MONITORENV_SENDER_EMAIL_ADDRESS, - cc: Union[str, List[str]] = None, - bcc: Union[str, List[str]] = None, - images: List[Path] = None, - attachments: dict = None, - reply_to: str = None, -) -> EmailMessage: - """ - Creates a `email.EmailMessage` with the defined parameters. - - Args: - to (Union[str, List[str]]): email address or list of email addresses of - recipient(s) - subject (str): Subject of the email. - html (str): html representation of the email's content. - from_ (str, optional): `From` field. Defaults to env var - `MONITORENV_SENDER_EMAIL_ADDRESS`. - cc (Union[str, List[str]], optional): `Cc` field with optional email address - (or list of email addresses) of copied recipient(s). Defaults to None. - bcc (Union[str, List[str]], optional): `Bcc` field with optional email address - (or list of email addresses) of hidden copied recipient(s). Defaults to None. - images (List[Path], optional): List of `Path` to images on the server's file - system to attach to the email. These images can be displayed in the html body - of the email by referencing them in the `src` attribute of an `` tag as - `cid:`, where `` is the image file's name. - - For example: `/a/b/c/my_image_123.png` can be included in the html message - as : - - `` in the html message. - - Defaults to None. - attachments (dict, optional): `dict` of attachments to add to the email. - Consists of {filename : bytes} value pairs. Defaults - to None. - reply_to (str, optional): if given, added as `Reply-To` header. Defaults to - None. - - Returns: - EmailMessage - """ - - if isinstance(to, list): - to = ", ".join(to) - - msg = EmailMessage() - msg["Subject"] = subject - msg["From"] = from_ - msg["To"] = to - - if cc: - if isinstance(cc, list): - cc = ", ".join(cc) - msg["Cc"] = cc - - if bcc: - if isinstance(bcc, list): - bcc = ", ".join(bcc) - msg["Bcc"] = bcc - - if reply_to: - msg["Reply-To"] = reply_to - - msg.set_content(html, subtype="html") - - if images: - for image in images: - (mimetype, _) = guess_type(image) - (maintype, subtype) = mimetype.split("/") - - with open(image, "rb") as f: - img_data = f.read() - msg.add_related( - img_data, - maintype=maintype, - subtype=subtype, - filename=image.name, - cid=f"<{image.name}>", - ) - - if attachments: - for filename, content in attachments.items(): - ctype, encoding = mimetypes.guess_type(filename) - if ctype is None or encoding is not None: - # No guess could be made, or the file is encoded (compressed), so - # use a generic bag-of-bits type. - ctype = "application/octet-stream" - maintype, subtype = ctype.split("/", 1) - msg.add_attachment( - content, - maintype=maintype, - subtype=subtype, - filename=filename, - ) - - return msg - - -def send_email(msg: EmailMessage) -> dict: - """ - Sends input email using the contents of `From` header as sender and `To`, `Cc` - and `Bcc` headers as recipients. - - This method will return normally if the mail is accepted for at least - one recipient. It returns a dictionary, with one entry for each - recipient that was refused. Each entry contains a tuple of the SMTP - error code and the accompanying error message sent by the server, like : - - { "three@three.org" : ( 550 ,"User unknown" ) } - - Args: - msg (EmailMessage): `email.message.EmailMessage` to send. - - Returns: - dict: {email_address : (error_code, error_message)} for all recipients that - were refused. - - Raises: - SMTPHeloError: The server didn't reply properly to the helo greeting. - SMTPRecipientsRefused: The server rejected ALL recipients (no mail was sent). - SMTPSenderRefused: The server didn't accept the from_addr. - SMTPDataError: The server replied with an unexpected error code (other than a - refusal of a recipient). - SMTPNotSupportedError: The mail_options parameter includes 'SMTPUTF8' but the - SMTPUTF8 extension is not supported by the server. - ValueError: if there is more than one set of 'Resent-' headers - """ - - assert EMAIL_SERVER_URL is not None - assert EMAIL_SERVER_PORT is not None - - with smtplib.SMTP(host=EMAIL_SERVER_URL, port=EMAIL_SERVER_PORT) as server: - send_errors = server.send_message(msg) - return send_errors diff --git a/datascience/src/pipeline/helpers/fao_areas.py b/datascience/src/pipeline/helpers/fao_areas.py deleted file mode 100644 index 4e9957909e..0000000000 --- a/datascience/src/pipeline/helpers/fao_areas.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import List, Sequence - - -def remove_redundant_fao_area_codes(s: Sequence[str]) -> List[str]: - """Filters the input sequence of FAO areas to keep only the smallest non - overlapping areas. - - This is useful to prune lists of FAO areas that result from intersecting a - geometry (ports, vessel position...) with all FAO areas. In such cases we only - want to keep the smallest (most precise) FAO areas in the result. - - Args: - s (Sequence[str]): list of FAO areas. - - Returns: - List[str]: subset of the input sequence. - - Examples: - >>> remove_redundant_fao_area_codes(['27.8.a', '27', '37.1']) - ['27.8.a', '37.1'] - """ - s = set(s) - return [a for a in s if True not in {a in t for t in (s - {a})}] diff --git a/datascience/src/pipeline/helpers/spatial.py b/datascience/src/pipeline/helpers/spatial.py deleted file mode 100644 index 828271de02..0000000000 --- a/datascience/src/pipeline/helpers/spatial.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -from dataclasses import dataclass -from typing import Union - -import pandas as pd -import requests -from dotenv import load_dotenv -from shapely.geometry import MultiPolygon, Polygon - -load_dotenv() - - -@dataclass -class Position: - latitude: float - longitude: float - - -def to_multipolygon(p: Union[Polygon, MultiPolygon]) -> MultiPolygon: - """ - Returns a MultiPolygon of the input Polygon or MultiPolygon geometry. - """ - - if isinstance(p, Polygon): - res = MultiPolygon([p]) - elif isinstance(p, MultiPolygon): - res = p - else: - raise ValueError("Input must be shapely Polygon or MultiPolygon") - - return res - - -def geocode(query_string=None, country_code_iso2=None, **kwargs): - """Return latitude, longitude for input location. - Provided either a query string, or one or more of the following keyword arguments: - street - city - county - state - country - postalcode - """ - base_url = "https://eu1.locationiq.com/v1/search.php" - params = {"key": os.environ["LOCATIONIQ_TOKEN"], "format": "json"} - - if query_string is not None and not pd.isna(query_string): - if kwargs: - print( - "Keyword arguments cannot be used in combination with text query. " - + "Keyword arguments will be ignored." - ) - - params["q"] = query_string - - elif kwargs: - params = {**params, **kwargs} - - else: - print( - "You must provide either a query string or at least 1 of the allowed " - + "keyword arguments." - ) - return None, None - - if country_code_iso2: - params["countrycodes"] = str(country_code_iso2) - - response = requests.get(base_url, params=params) - response.raise_for_status() - data = response.json()[0] - return float(data["lat"]), float(data["lon"]) diff --git a/datascience/src/pipeline/helpers/strings.py b/datascience/src/pipeline/helpers/strings.py deleted file mode 100644 index 4193067757..0000000000 --- a/datascience/src/pipeline/helpers/strings.py +++ /dev/null @@ -1,17 +0,0 @@ -import re - -def to_snake_case(s: str) -> str: - # Exemple : "DateOfInformation" -> "date_of_information" - # "IMONumber" -> "imo_number" - # "ShipID" -> "ship_id" - - # Insere un _ avant une majuscule suivie d’une minuscule - s = re.sub(r'(?<=[a-z0-9])([A-Z])', r'_\1', s) - - # Gestion des acronymes - s = re.sub(r'([A-Z])([A-Z][a-z])', r'\1_\2', s) - - # Remplace espaces et caractères non alphanumériques - s = re.sub(r'\W+', '_', s) - - return s.lower().strip('_') \ No newline at end of file diff --git a/datascience/src/pipeline/processing.py b/datascience/src/pipeline/processing.py deleted file mode 100644 index 6f8e1714fe..0000000000 --- a/datascience/src/pipeline/processing.py +++ /dev/null @@ -1,903 +0,0 @@ -import datetime -import logging -from functools import partial -from typing import Any, Hashable, List, Union - -import numpy as np -import pandas as pd -import pytz -import simplejson -import sqlalchemy -from sqlalchemy import select - - -def get_unused_col_name(col_name: str, df: pd.DataFrame) -> str: - """ - If `col_name` is not already a column name of the DataFrame `df`, returns - `col_name`. Otherwise, appends a number to `col_name`, trying 0, 1, 2, ... - until a unused column name if found. - - Args: - col_name (str): desired column name - df (pd.DataFrame): DataFrame for which we want to ensure the column name is not - already used - - Returns: - str: column name - - Examples: - >>> get_unused_col_name("id", pd.DataFrame({"idx": [1, 2, 3]})) - "id" - - >>> get_unused_col_name("id", pd.DataFrame({"id": [1, 2, 3]})) - "id_0" - - >>> get_unused_col_name("id", pd.DataFrame({"id": [1, 2, 3], "id_0": [4, 5, 6]})) - "id_1" - """ - df_columns = list(df) - attempt_col_name = col_name - while attempt_col_name in df_columns: - if "i" not in locals(): - i = 0 - else: - i += 1 - attempt_col_name = f"{col_name}_{i}" - - return attempt_col_name - - -def is_a_value(x) -> bool: - """Returns False if pd.isna(x), True otherwise. - - NB : The same result could be obtained simply by checking pd.isna(x), - but checking if x is None before checking pd.isna(x) - improves performance on DataFrames containing many None values, - since checking pd.isna(x) is slower than checking if x is None. - - Args: - x : Anything - - Returns: - bool: False if pd.isna(x), True otherwise - """ - if x is not None and not pd.isna(x): - return True - else: - return False - - -def concatenate_values(row: pd.Series) -> List: - """Filters the input pandas Series to keep only distinct non null values - and returns the result as a python list. - - Args: - row (pd.Series): pandas Series - - Returns: - List: list of distinct non null values in row - """ - result_set = set() - res = [] - for x in row: - if is_a_value(x) and x not in result_set: - res.append(x) - result_set.add(x) - return res - - -def concatenate_columns(df: pd.DataFrame, input_col_names: List) -> pd.Series: - """For each row in the input DataFrame, the distinct and non null values contained in - the columns input_col_names are stored in a list. A pandas Series of the same length - as the input DataFrame is then constructed with these lists as values. - - Args: - df (pd.DataFrame): input DataFrame - input_col_names (List): the names of the columns to use - - Returns: - pd.Series: resulting Series - """ - non_null_rows = df[input_col_names].dropna(how="all") - res_non_null_rows = non_null_rows.apply(concatenate_values, axis=1) - res = pd.Series(index=df.index, data=[[]] * len(df)) - res[res_non_null_rows.index] = res_non_null_rows.values - return res - - -def coalesce(df: pd.DataFrame) -> pd.Series: - """Combines the input DataFrame's columns into one by taking the non null value in - each row, in the order of the DataFrame's columns from left to right. - - Returns a pandas Series with the combined results. - - Args: - df (pd.DataFrame): input pandas DataFrame - - Returns: - pd.Series: Series containing the first non null value in each row of the - DataFrame, taken in order of the DataFrame's columns from left to right. - """ - non_null_rows = df.dropna(how="all") - first_non_null_values_idx = np.argmax( - non_null_rows.notnull().values, axis=1 - ) - - res_values = np.choose(first_non_null_values_idx, non_null_rows.values.T) - - res = pd.Series(index=df.index, data=[None] * len(df), dtype=object) - res[non_null_rows.index] = res_values - return res - - -def get_first_non_null_column_name( - df: pd.DataFrame, result_labels: Union[None, dict] = None -) -> pd.Series: - """Returns a Series with the same index as the input DataFrame, whose values are - the name of the first column (or the corresponding label, if provided) with a - non-null value in each row, from left to right. - - Rows with all null values return None. - - Args: - df (pd.DataFrame): input pandas DataFrame - result_labels (dict): if provided, must be a mapping of column names to the - corresponding labels in the result. - - Returns: - pd.Series: Series containing the name of the first column with a non-null value - in each row of the DataFrame, from left to right - """ - - non_null_rows = df.dropna(how="all") - first_non_null_values_idx = np.argmax( - non_null_rows.notnull().values, axis=1 - ) - - res_values = np.choose(first_non_null_values_idx, list(df)) - - res = pd.Series(index=df.index, data=[None] * len(df), dtype=float) - res[non_null_rows.index] = res_values - - if result_labels is not None: - res = res.map(lambda s: result_labels.get(s)) - - return res - - -def remove_nones_from_dict(d: dict) -> dict: - """ - Takes a dictionary and removes ``None`` values from it. - - Args: - d (dict): a dictionary - - Returns: - dict: the input dictionary, with all ``None``s removed. - - Examples: - >>> d = { - "a" : 1, - "b": [1, 2, None], - "c": {"key": "value", "key2": None}, - "d": None - } - >>> remove_nones_from_dict(d) - {"a" : 1, "b": [1, 2, None], "c": {"key": "value", "key2": None}} - """ - return {k: v for k, v in d.items() if v is not None} - - -def df_to_dict_series( - df: pd.DataFrame, - result_colname: str = "json_col", - remove_nulls: bool = False, -): - """Converts a pandas DataFrame into a Series with the same index as the input - DataFrame and whose values are dictionaries like : - - {'column_1' : value, 'column_2': value} - - Args: - df (pd.DataFrame): input DataFrame - result_colname (Union[str, None]): optionnal, name of result Series - remove_nulls (bool): if set to ``True``, ``null`` values are recursively - removed from the dictionaries - - Returns: - pd.Series: pandas Series - """ - res = df.copy(deep=True) - json_string = res.to_json(orient="index") - - res = pd.read_json(json_string, orient="index", typ="Series") - res.name = result_colname - - if remove_nulls: - res = res.map(remove_nones_from_dict) - - return res - - -def zeros_ones_to_bools( - x: Union[pd.Series, pd.DataFrame] -) -> Union[pd.Series, pd.DataFrame]: - """Converts a pandas DataFrame or Series containing `str`, `int` or `float` values, - possibly including null (`None` and `np.nan`) values to a DataFrame with False, - True and `np.nan` values respectively. - - Values 1, 1.0, "1", any non zero number... is converted to `True`. - Values 0, 0.0, "0" are converted to `False`. - Values `None` and `np.nan` are converted to `np.nan`. - - Useful to convert boolean data extracted from Oracle databases, since Oracle does - not have a boolean data type and boolean data is often stored as "0"s and "1"s, - or to handle sitations in which pandas data structures should contain nullable - boolean data (in pandas / numpy, the `bool` dtype is not nullable, and this can - be tricky to handle). - """ - tmp = x.astype(float) - return tmp.where(~((tmp > 0) | (tmp < 0)), True).replace([0.0], False) - - -def to_pgarr( - x: Union[list, set, np.ndarray], - handle_errors: bool = False, - value_on_error: Union[str, None] = None, -) -> Union[str, None]: - """Converts a python `list`, `set` or `numpy.ndarray` to a string with Postgresql - array syntax. - - Elements of the list-like input argument are converted to `string` type, then - stripped of leading and trailing blank spaces, and finally filtered to keep only - non empty strings. - - This transformation is required on the elements of a DataFrame's columns that - contain collections before bulk inserting the DataFrame into Postgresql with - the psql_insert_copy method. - - Args: - x (list, set or numpy.ndarray) : iterable to serialize as Postgres array - handle_errors (bool): if ``True``, returns ``value_on_error`` instead of raising - ``ValueError`` when the input is of an unexpected type - value_on_error (str or None): value to return on errors, if ``handle_errors`` - is ``True`` - - Returns: - str: string with Postgresql Array compatible syntax - - Raises: - ValueError : when ``handle_errors`` is False and ``x`` is not list-like. - - Examples: - >>> to_pgarr([1, 2, "a ", "b", "", " "]) - "{1,2,'a','b'}" - >>> to_pgarr(None) - ValueError - - >>> to_pgarr(None, handle_errors=True, value_on_error="{}") - "{}" - - >>> to_pgarr(np.nan, handle_errors=True, value_on_error=None) - """ - try: - assert isinstance(x, (list, set, np.ndarray)) - - except AssertionError: - if handle_errors: - return value_on_error - else: - raise ValueError(f"Unexpected type for x: {type(x)}.") - - return ( - "{" - + ",".join(filter(lambda x: len(x) > 0, map(str.strip, map(str, x)))) - + "}" - ) - - -def df_values_to_psql_arrays( - df: pd.DataFrame, - handle_errors: bool = False, - value_on_error: Union[str, None] = None, -) -> pd.DataFrame: - """Returns a `pandas.DataFrame` with all values serialized as strings - with Postgresql array syntax. All values must be of type list, set or numpy array. - Other values raise errors, which may be handled if handle_errors is set to True. - - See `to_pgarr` for details on error handling. - - This is required before bulk loading a pandas.DataFrame into a Postgresql table - with the psql_insert_copy method. - - - Args: - df (pd.DataFrame): pandas DataFrame - - Returns: - pd.DataFrame: pandas DataFrame with the same shape and index, all values - serialized as strings with Postgresql array syntax. - - Examples : - - >>> df_to_psql_arrays(pd.DataFrame({'a': [[1, 2], ['a', 'b']]})) - a - 0 {1,2,3} - 1 {a,b} - """ - - serialize = partial( - to_pgarr, handle_errors=handle_errors, value_on_error=value_on_error - ) - - return df.applymap(serialize, na_action="ignore").fillna("{}") - - -def json_converter(x): - """Converter for types not natively handled by json.dumps""" - if isinstance(x, np.ndarray): - return x.tolist() - if isinstance(x, pd._libs.tslibs.nattype.NaTType): - return None - if isinstance(x, datetime.datetime): - if x.tzinfo: - x = x.replace(tzinfo=pytz.timezone("UTC")) - x.utcoffset() - return x.isoformat().replace("+00:00", "Z") # UTC, ISO format - else: - return x.isoformat() + "Z" - elif isinstance(x, datetime.date): - return x.isoformat() - - -def to_json(x: Any) -> str: - """Converts python object to json string.""" - - res = simplejson.dumps( - x, ensure_ascii=False, default=json_converter, ignore_nan=True - ) - - return res - - -def df_values_to_json(df: pd.DataFrame) -> pd.DataFrame: - """Returns a `pandas.DataFrame` with all values serialized to json string. - - This is required before bulk loading into a Postgresql table with - the psql_insert_copy method. - - See `to_json` function for details. - - Args: - df (pd.DataFrame): pandas DataFrame - - Returns: - pd.DataFrame: pandas DataFrame with the same shape and index, all values - serialized as json strings. - """ - return df.applymap(to_json, na_action="ignore").fillna("null") - - -def serialize_nullable_integer_df(df: pd.DataFrame) -> pd.DataFrame: - """Serializes the values of a DataFrame that contains numbers that represent - possibly null (np.nan or None) integers. This is useful to prepare data before - loading to integer Postgres columns, as pandas automatically converts integer - Series to float dtype if they contain nulls. - - Args: - df (pd.DataFrame): DataFrame of integer, possibly with None and np.nan values - - Returns: - pd.DataFrame: same DataFrame converted to string dtype - """ - return df.applymap(lambda x: str(int(x)), na_action="ignore").where( - df.notnull(), None - ) - - -def serialize_timedelta_df(df: pd.DataFrame) -> pd.DataFrame: - """Serializes the values of a DataFrame that contains `timedelta` values. - This is useful to prepare data before loading to `interval` Postgres columns, as - sqlachemy does not support the timedelta dtype. - - Args: - df (pd.DataFrame): DataFrame of timedeltas - - Returns: - pd.DataFrame: same DataFrame converted to string dtype - """ - return df.astype("timedelta64[ns]").astype(str).replace(["NaT"], [None]) - - -def drop_rows_already_in_table( - df: pd.DataFrame, - df_column_name: str, - table: sqlalchemy.Table, - table_column_name: str, - connection: sqlalchemy.engine.base.Connection, - logger: logging.Logger, -) -> pd.DataFrame: - """Removes rows from the input DataFrame `df` in which the column `df_column_name` - contains values that are already present in the column `table_column_name` of the - table `table`, and returns the filtered DataFrame.""" - - df_n_rows = len(df) - df_ids = tuple(df[df_column_name].unique()) - df_n_ids = len(df_ids) - - statement = select(getattr(table.c, table_column_name)).where( - getattr(table.c, table_column_name).in_(df_ids) - ) - - df_ids_already_in_table = tuple( - pd.read_sql(statement, connection)[table_column_name] - ) - - # Remove keys already present in the database table from df - res = df[~df[df_column_name].isin(df_ids_already_in_table)] - - # Remove possible duplicate ids in df - res = res[~res[df_column_name].duplicated()] - - res_n_rows = len(res) - res_n_ids = res[df_column_name].nunique() - - log = ( - f"From {df_n_rows} rows with {df_n_ids} distinct {df_column_name} values, " - + f"{res_n_rows} rows with {res_n_ids} distinct {df_column_name} values " - + "are new and will be inserted in the database." - ) - - logger.info(log) - return res - - -def prepare_df_for_loading( - df: pd.DataFrame, - logger: logging.Logger, - pg_array_columns: Union[None, list] = None, - handle_array_conversion_errors: bool = True, - value_on_array_conversion_error="{}", - jsonb_columns: Union[None, list] = None, - nullable_integer_columns: Union[None, list] = None, - timedelta_columns: Union[None, list] = None, -): - - df_ = df.copy(deep=True) - - # Serialize columns to be loaded into JSONB columns - if jsonb_columns: - logger.info("Serializing json columns") - df_[jsonb_columns] = df_values_to_json(df_[jsonb_columns]) - - # Serialize columns to be loaded into Postgres ARRAY columns - if pg_array_columns: - logger.info("Serializing postgresql array columns") - df_[pg_array_columns] = df_values_to_psql_arrays( - df_[pg_array_columns], - handle_errors=handle_array_conversion_errors, - value_on_error=value_on_array_conversion_error, - ) - - # Serialize columns that contain nullable integers (stored an float in python) - if nullable_integer_columns: - logger.info("Serializing nullable integer columns") - df_[nullable_integer_columns] = serialize_nullable_integer_df( - df_[nullable_integer_columns] - ) - - if timedelta_columns: - logger.info("Serializing timedelta columns") - df_[timedelta_columns] = serialize_timedelta_df(df_[timedelta_columns]) - - return df_ - - -def join_on_multiple_keys( - left: pd.DataFrame, right: pd.DataFrame, on: list, how: str = "inner" -): - """Join two pandas DataFrames, attempting to match rows on several keys by - decreasing order of priority. - - Joins are performed successively with each of the keys listed in `on`, and results - are then concatenated to form the final result. This is different from joining on a - composite key where all keys must match simultaneously : here, rows of left and - right DataFrames are joined if at least one of the keys match. - - Joins are performed on the keys listed in `on` by "decreasing order or priority" in - the sense that rows of left and right that have been matched on one key are removed - from ulterior joins performed on the next keys. - - During each of the joins on the individual keys, non-joining key pairs and, if any, - columns common to both left and right DataFrames, are coalesced (from left to - right). - - Args: - left (pd.DataFrame): pandas DataFrame - right (pd.DataFrame): pandas DataFrame - on (list): list of column names to use as join keys - how (str): 'inner', 'left', 'right' or 'outer'. Defaults to 'inner'. - - Returns: - pd.DataFrame: result of join operation - """ - - joins = [] - common_columns = set.intersection(set(left.columns), set(right.columns)) - keys_already_joined = set() - - # Attempt to perform the join successively on each key - for key in on: - - right_with_key = right.dropna(subset=[key]) - left_with_key = left.dropna(subset=[key]) - - join = pd.merge( - left_with_key, - right_with_key, - on=key, - how="inner", - suffixes=("_left", "_right"), - ) - - columns_to_merge = common_columns - {key} - - for column_to_merge in columns_to_merge: - - [l, r] = [f"{column_to_merge}_left", f"{column_to_merge}_right"] - - if column_to_merge in keys_already_joined: - join = join[(join[r].isna()) | (join[l].isna())] - - join[column_to_merge] = coalesce(join[[l, r]]) - - join = join.drop(columns=[l, r]) - - left = left[~left[key].isin(join[key])] - right = right[~right[key].isin(join[key])] - - keys_already_joined.add(key) - joins.append(join) - - # Add unmatched rows if performing left, right or outer joins - if how in ("left", "outer"): - joins.append(left) - - if how in ("right", "outer"): - joins.append(right) - - # Concatenate all join results - res = pd.concat(joins, axis=0) - res.index = np.arange(0, len(res)) - - columns_order = list(left) + [col for col in right if col not in left] - res = res[columns_order] - - return res - - -def left_isin_right_by_decreasing_priority( - left: pd.DataFrame, right: pd.DataFrame -) -> pd.Series: - """ - Performs an operation similar to `pandas.DataFrame.isin` on multiple columns, with - the differences that : - - - the columns are tested one by one (instead of being tested simultaneously as in - the case of `pandas.DataFrame.isin`), the first column of `left` being tested - against the first column of `right`, the second column of `left` being tested - against the second column of `right`... - - columns are considered to be sorted by decreasing priority, meaning that a match - on 2 rows of `left` and `right` on a given column will be taken into account only - if the columns of higher priority on those 2 rows have values that are either equal - or null. - - Takes two DataFrames `left` and `right` with the same columns, returns a Series - with the same index as the `left` DataFrame and whose values are : - - - `True` if the corresponding row in `left` has a match in `right' in at least one - column - - `False` if the corresponding row in `left` has no match in `right' - - This is typically useful to filter vessels' data based on some other vessels' data, - both datasets being index with multiple identifiers (cfr, ircs, external immat...). - - Args: - left (pd.DataFrame): DataFrame - right (pd.DataFrame): DataFrame with values for which to test if they are - present in `left` - - Returns: - List[bool]: list of booleans with the same length as `left` - """ - - assert list(left) == list(right) - - left = left.copy(deep=True) - right = right.copy(deep=True) - cols = list(left) - - id_col = get_unused_col_name("id", left) - left[id_col] = np.arange(len(left)) - - isin_right_col = get_unused_col_name("isin_right", right) - right[isin_right_col] = True - - res = join_on_multiple_keys(left, right, on=cols, how="left") - res = ( - res.drop_duplicates(subset=[id_col]) - .sort_values(id_col)[isin_right_col] - .fillna(False) - ) - res.index = left.index - - return res - - -def drop_duplicates_by_decreasing_priority( - df: pd.DataFrame, subset: List[str] -) -> pd.DataFrame: - """Similar to `pandas.DataFrame.drop_duplicates(subset=subset)`, with the - differences that: - - - the rows are deduplicated based on their values in the columns in `subset` one - after the other and by decreasing priority, and not simultaneously - - `NA` values on a key are not considered - - Rows having all `NA` values in all columns of `subset` are dropped. - - What is meant by "by decreasing proirity" is that keys in `subset` are considered - to be sorted by decresing level of priority (for instance `A` and `B`, with `A` - having the highest level of priority), and rows with distinct values on `B` but - identical values on `A` will be considered duplicated, whereas rows with distinct - values on `A` and identical values on `B` will not be considered duplicates. Hence, - the first key in `subset` entirely determines whether rows are duplicates or not on - all rows with non null `A`, and subsequent keys in `subset` only come into play on - rows where `A` is null. - - This is typically useful to deduplicate data containing one row per vessel with - potential duplicates but with multiple identifier columns (cfr, external - immatriculation, ircs), some identifiers being more reliable than others. For - instance, if two rows have the same CFR but different external immatriculation, it - is reasonable to assume that it is a one the same vessel, whereas two rows wihout - any information on CFR and different external immats should be considered as two - distinct vessels. - - Args: - df (pd.DataFrame): Input DataFrame - id_cols (List[str]): List of column names to use as keys for the - `drop_duplicates` operation, by decreasing level of priority - - Returns: - pd.DataFrame: Copy of the input DataFrame with duplicate rows removed. - """ - try: - assert isinstance(subset, list) - except AssertionError: - raise TypeError("`subset` must be a list.") - - try: - assert len(subset) >= 1 - except AssertionError: - raise TypeError("`subset` must not be empty.") - - if len(subset) == 1: - res = df.dropna(subset=subset).drop_duplicates(subset=subset) - - else: - first_key_not_null = df.dropna(subset=[subset[0]]).drop_duplicates( - subset=[subset[0]] - ) - - first_key_null = drop_duplicates_by_decreasing_priority( - df[df[subset[0]].isna()], subset=subset[1:] - ) - - first_key_null = first_key_null[ - ~left_isin_right_by_decreasing_priority( - first_key_null[subset], first_key_not_null[subset] - ) - ] - - res = pd.concat([first_key_not_null, first_key_null]) - - return res - - -def try_get_factory(key: Hashable, error_value: Any = None): - def try_get(d: Any) -> Any: - """ - Attempt to fetch an element from what is supposed to be dict (but may not be), - return error_value if it fails (for any reason). - - This is useful to extract values from a series of dictionnaries which may not all - contain the searched key. It is faster than checking for the presence of the key - each time. - """ - - try: - return d[key] - except: - return error_value - - return try_get - - -def array_equals_row_on_window( - arr: np.array, row: np.array, window_length: int -) -> np.array: - """Tests whether each row of an input 2D array is the last of a sequence of - `window_length` consecutive rows equal to a given `row` 1D array, and returns the - result as a float array with the same length as the input array. - - The output array is of `float` dtype and not `bool` dtype, because numpy `bool` - arrays cannot contain null values. The values are `0.0` (representing `False`), - `1.0` (representing `True`) and `np.nan` representing nulls. - - The first (`window_length` - 1) rows evaluate to `np.nan`, since the sliding window - would need to know the values of the previous rows which are not given. - - Args: - arr (np.array): 2D numpy array - row (np.array): 1D numpy array with the same length as the number of columns in - `arr` - window_length (int): number of consecutive rows that must be equal to `row` for - the result to be `True` - - Returns: - np.array: 1D boolean array of the same length as the input arrays - - - Examples: - >>> arr = np.array([ - [False, True], - [False, True], - [True, True], - [False, True], - [False, True], - ]) - >>> row = np.array([False, True]) - >>> array_equals_row_on_window(arr, row, 2) - array([nan, 1., 0., 0., 1.]) - """ - - n_rows, n_columns = arr.shape - - # When the sliding window has more rows that the input array, return all nulls - if n_rows < window_length: - res = np.array([np.nan] * n_rows) - - else: - - strides = np.lib.stride_tricks.sliding_window_view( - arr, (window_length, n_columns) - ) - - res = (strides == row).all(axis=(1, 2, 3)) - - number_na_rows_to_add = window_length - 1 - - na_rows_to_add = np.array([np.nan] * number_na_rows_to_add) - - res = np.concatenate((na_rows_to_add, res)) - - return res.astype(float) - - -def back_propagate_ones(arr: np.array, steps: int) -> np.array: - """ - Given a 1D array with values `0.0`, `1.0` and `np.nan`, propagates `1.0` backward - `steps` times. - - Args: - arr (np.array): array containing `0.0`, `1.0` and `np.nan` values - steps (int): number of steps that ones should be back-propagated - - returns: - np.array: 1D array with the same dimensions as input, with ones back-propagated - `steps` times. - - Examples: - >>> arr = np.array([np.nan, 0., 0., 1., 0., 0., 1., 1., 0., 1.]) - >>> back_propagate_ones(arr, 1) - array([nan, 0., 1., 1., 0., 1., 1., 1., 1., 1.]) - """ - if steps == 0: - return arr - else: - previous_step = back_propagate_ones( - np.append(arr[1:], np.nan), steps - 1 - ) - tmp = np.concatenate((arr[:, None], previous_step[:, None]), axis=1) - - ones = np.equal(tmp, 1).any(axis=1) - nans = np.isnan(tmp).any(axis=1) - res = np.where((nans & (~ones)), np.nan, ones) - return res - - -def rows_belong_to_sequence( - arr: np.array, row: np.array, window_length: int -) -> np.array: - """Tests whether each row of an input 2D array belongs to a sequence of - `window_length` consecutive rows equal to a given `row` 1D array, and returns the - result as a float array with the same length as the input array. - - The output array is of `float` dtype and not `bool` dtype, because numpy `bool` - arrays cannot contain null values. The values are `0.0` (representing `False`), - `1.0` (representing `True`) and `np.nan` representing nulls. - - The first and last (`window_length` - 1) rows may be `np.nan`, since the rows - before the beginning and after the end of the array are not known and might be - needed to determine the result. - - Args: - arr (np.array): 2D numpy array - row (np.array): 1D numpy array with the same length as the number of columns in - `arr` - window_length (int): number of consecutive rows that must be equal to `row` for - the result to be `True` - - Returns: - np.array: 1D boolean array of the same length as the input arrays - - - Examples: - >>> arr = np.array([ - [False, True], - [False, True], - [True, True], - [False, True], - [False, True], - ]) - >>> row = np.array([False, True]) - >>> rows_belong_to_sequence(arr, row, 2) - array([1., 1., 0., 1., 1.]) - - >>> arr = np.array([ - [False, True], - [True, True], - [True, True], - [False, True], - [False, True], - [False, False] - ]) - >>> row = np.array([False, True]) - >>> rows_belong_to_sequence(arr, row, 2) - array([nan, 0., 0., 1., 1., 0.]) - """ - ends_of_sequences = array_equals_row_on_window( - arr, - row, - window_length=window_length, - ) - - rows_known = back_propagate_ones( - ends_of_sequences, steps=window_length - 1 - ) - - # To test if rows at the beginning and at the end of the array could possibly - # belong to a sequence `row`s exceeding the boundaries of the array, we add rows to - # the array and test again - extended_arr = np.concatenate( - ( - row * np.ones((window_length - 1, len(row))), - arr, - row * np.ones((window_length - 1, len(row))), - ) - ) - - ends_of_sequences_extended = array_equals_row_on_window( - extended_arr, - row, - window_length=window_length, - ) - - rows_maybe = back_propagate_ones( - ends_of_sequences_extended, steps=window_length - 1 - )[window_length - 1 : -(window_length - 1)] - - res = np.where( - np.isnan(rows_known) & rows_maybe.astype(bool), np.nan, rows_maybe - ) - - return res diff --git a/datascience/src/pipeline/queries/cross/cacem/amp.sql b/datascience/src/pipeline/queries/cross/cacem/amp.sql deleted file mode 100644 index f5746c753e..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/amp.sql +++ /dev/null @@ -1,23 +0,0 @@ -SELECT id, -st_multi(ST_SimplifyPreserveTopology(ST_CurveToLine(geom), 0.00001)) geom, -mpa_oriname, -des_desigfr, -mpa_type, -ref_reg, -url_legicem, -md5( - coalesce(id::text,'')|| - coalesce(geom::text,'')|| - coalesce(mpa_oriname,'')|| - coalesce(des_desigfr,'')|| - coalesce(mpa_type,'') || - coalesce(ref_reg,'')|| - coalesce(url_legicem,'') - ) as row_hash - FROM prod."Aires marines protégées" - WHERE - geom IS NOT NULL - AND st_isvalid(geom) - AND mpa_oriname IS NOT NULL - AND des_desigfr IS NOT NULL - AND id IN :ids \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/amp_hashes.sql b/datascience/src/pipeline/queries/cross/cacem/amp_hashes.sql deleted file mode 100644 index 3af7e57cb8..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/amp_hashes.sql +++ /dev/null @@ -1,17 +0,0 @@ -SELECT id, -md5( - coalesce(id::text,'')|| - coalesce(geom::text,'')|| - coalesce(mpa_oriname,'')|| - coalesce(des_desigfr,'')|| - coalesce(mpa_type,'') || - coalesce(ref_reg,'')|| - coalesce(url_legicem,'') - ) as cacem_row_hash - FROM - prod."Aires marines protégées" - WHERE - geom IS NOT NULL - AND st_isvalid(geom) - AND mpa_oriname IS NOT NULL - AND des_desigfr IS NOT NULL \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/beaches.sql b/datascience/src/pipeline/queries/cross/cacem/beaches.sql deleted file mode 100644 index 73d4dcb9c2..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/beaches.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - id, - nom as "name", - insee, - nom_offici as official_name, - geom, - code_posta as postcode -FROM prod.plageslitto; \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/competence_cross_areas.sql b/datascience/src/pipeline/queries/cross/cacem/competence_cross_areas.sql deleted file mode 100644 index 4e3468090e..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/competence_cross_areas.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT id, - geom, - "name", - "description", - "timestamp", - "begin", - "end", - altitude_mode, - tessellate, - extrude, - visibility, - draw_order, - icon -FROM prod.competence_cross_areas diff --git a/datascience/src/pipeline/queries/cross/cacem/localized_areas.sql b/datascience/src/pipeline/queries/cross/cacem/localized_areas.sql deleted file mode 100644 index cf57afe6c5..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/localized_areas.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - id, - amp_ids, - control_unit_ids, - group_name, - geom, - "name" -FROM prod.localized_areas -WHERE geom IS NOT NULL - AND "name" IS NOT NULL; \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/low_water_line.sql b/datascience/src/pipeline/queries/cross/cacem/low_water_line.sql deleted file mode 100644 index 1a80ca6b23..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/low_water_line.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - ogc_fid, - gid, - gml_id, - cdlaisseea, - typelaisse, - geom -FROM prod.low_water_line; \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/marpol.sql b/datascience/src/pipeline/queries/cross/cacem/marpol.sql deleted file mode 100644 index a4d9eadc46..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/marpol.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - id, - geom, - zone, - zone_neca, - zone_seca - FROM prod.marpol diff --git a/datascience/src/pipeline/queries/cross/cacem/regulations.sql b/datascience/src/pipeline/queries/cross/cacem/regulations.sql deleted file mode 100644 index 7c2686ac24..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/regulations.sql +++ /dev/null @@ -1,43 +0,0 @@ -SELECT - id, - st_multi(ST_SimplifyPreserveTopology(ST_CurveToLine(geom), 0.00001)) geom, - ent_name AS entity_name, - url, - layer_name, - facade, - ref_reg, - editeur, - source, - obs AS observation, - thematique, - validite AS duree_validite, - tempo AS temporalite, - type, - date, - date_fin, - edition, - md5( - COALESCE(geom::text, '') || - COALESCE(ent_name::text, '') || - COALESCE(url::text, '') || - COALESCE(layer_name::text, '') || - COALESCE(facade::text, '') || - COALESCE(ref_reg::text, '') || - COALESCE(editeur::text, '') || - COALESCE(source::text, '') || - COALESCE(obs::text, '') || - COALESCE(thematique::text, '') || - COALESCE(validite::text, '') || - COALESCE(tempo::text, '') || - COALESCE(type::text, '') || - COALESCE(date::text, '') || - COALESCE(date_fin::text, '') || - COALESCE(edition::text, '') - ) as row_hash -FROM prod."REG_ENV_V3" -WHERE - geom IS NOT NULL - AND ent_name IS NOT NULL - AND layer_name IS NOT NULL - AND thematique IS NOT NULL - AND id IN :ids diff --git a/datascience/src/pipeline/queries/cross/cacem/regulations_hashes.sql b/datascience/src/pipeline/queries/cross/cacem/regulations_hashes.sql deleted file mode 100644 index 5ebbd97b03..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/regulations_hashes.sql +++ /dev/null @@ -1,26 +0,0 @@ -SELECT - id, - md5( - COALESCE(geom::text, '') || - COALESCE(ent_name::text, '') || - COALESCE(url::text, '') || - COALESCE(layer_name::text, '') || - COALESCE(facade::text, '') || - COALESCE(ref_reg::text, '') || - COALESCE(editeur::text, '') || - COALESCE(source::text, '') || - COALESCE(obs::text, '') || - COALESCE(thematique::text, '') || - COALESCE(validite::text, '')|| - COALESCE(tempo::text, '')|| - COALESCE(type::text, '') || - COALESCE(date::text, '') || - COALESCE(date_fin::text, '') || - COALESCE(edition::text, '') - ) AS cacem_row_hash -FROM prod."REG_ENV_V3" -WHERE - geom IS NOT NULL - AND ent_name IS NOT NULL - AND layer_name IS NOT NULL - AND thematique IS NOT NULL \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/regulations_open_data.sql b/datascience/src/pipeline/queries/cross/cacem/regulations_open_data.sql deleted file mode 100644 index cbfd38d26e..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/regulations_open_data.sql +++ /dev/null @@ -1,23 +0,0 @@ -SELECT - id, - ent_name, - url, - layer_name, - facade, - ref_reg, - edition::timestamp, - editeur, - source, - thematique, - obs, - date::timestamp, - date_fin::timestamp, - validite, - tempo, - type, - ST_ASTEXT(ST_CurveToLine(geom)) AS wkt, - resume, - poly_name, - plan, - geom as geometry -FROM prod."REG_ENV_V3" \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/semaphore.sql b/datascience/src/pipeline/queries/cross/cacem/semaphore.sql deleted file mode 100644 index c05b69c8ae..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/semaphore.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - id, - geom, - nom, - dept, - facade, - administration, - unite, - email, - telephone, - base, - "url" -FROM prod.semaphore -WHERE geom IS NOT NULL and nom IS NOT NULL \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/straight_baseline.sql b/datascience/src/pipeline/queries/cross/cacem/straight_baseline.sql deleted file mode 100644 index b73112b11a..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/straight_baseline.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - ogc_fid, - nature, - type, - descriptio, - reference, - beginlifes, - territory, - country, - agency, - inspireid, - geom - FROM prod.straight_baseline - WHERE geom IS NOT NULL; \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/tags.sql b/datascience/src/pipeline/queries/cross/cacem/tags.sql deleted file mode 100644 index 8a53254743..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/tags.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - id, - "name", - parent_id, - started_at, - ended_at -FROM prod.tags -WHERE id IN :ids \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/tags_hashes.sql b/datascience/src/pipeline/queries/cross/cacem/tags_hashes.sql deleted file mode 100644 index d22c29c9bf..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/tags_hashes.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - id, - "name", - parent_id, - started_at, - ended_at, - md5( - COALESCE("name"::VARCHAR, '') || - COALESCE(parent_id::INT, 0) || - COALESCE(started_at::TIMESTAMP, NOW()) || - COALESCE(ended_at::TIMESTAMP, NOW()) - ) AS cacem_row_hash -FROM prod.tags \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/tags_regulatory_areas.sql b/datascience/src/pipeline/queries/cross/cacem/tags_regulatory_areas.sql deleted file mode 100644 index 20b1fdb93a..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/tags_regulatory_areas.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - tags_id, - regulatory_areas_id -FROM prod.tags_regulatory_areas -INNER JOIN prod."REG_ENV_V3" regulatory_areas ON regulatory_areas_id = regulatory_areas.id -WHERE regulatory_areas.geom IS NOT NULL - AND regulatory_areas.ent_name IS NOT NULL - AND regulatory_areas.layer_name IS NOT NULL -ORDER BY tags_id \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/territorial_seas.sql b/datascience/src/pipeline/queries/cross/cacem/territorial_seas.sql deleted file mode 100644 index f30f5c9809..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/territorial_seas.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - ogc_fid, - inspireid, - type, - descriptio, - surface, - reference, - beginlifes, - territory, - country, - agency, - geom -FROM prod.territorial_seas -WHERE geom IS NOT NULL diff --git a/datascience/src/pipeline/queries/cross/cacem/themes.sql b/datascience/src/pipeline/queries/cross/cacem/themes.sql deleted file mode 100644 index 2f2bc567fc..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/themes.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - id, - "name", - parent_id, - started_at, - ended_at, - control_plan_themes_id, - control_plan_sub_themes_id, - control_plan_tags_id, - reportings_control_plan_sub_themes_id -FROM prod.themes -WHERE id IN :ids \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/themes_hashes.sql b/datascience/src/pipeline/queries/cross/cacem/themes_hashes.sql deleted file mode 100644 index 77901f8755..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/themes_hashes.sql +++ /dev/null @@ -1,21 +0,0 @@ -SELECT - id, - "name", - parent_id, - started_at, - ended_at, - control_plan_themes_id, - control_plan_sub_themes_id, - control_plan_tags_id, - reportings_control_plan_sub_themes_id, - md5( - COALESCE("name"::VARCHAR, '') || - COALESCE(parent_id::INT, 0) || - COALESCE(started_at::TIMESTAMP, NOW()) || - COALESCE(ended_at::TIMESTAMP, NOW()) || - COALESCE(control_plan_themes_id::INT, 0) || - COALESCE(control_plan_sub_themes_id::INT, 0) || - COALESCE(control_plan_tags_id::INT, 0) || - COALESCE(reportings_control_plan_sub_themes_id::INT, 0) - ) AS cacem_row_hash -FROM prod.themes \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/themes_regulatory_areas.sql b/datascience/src/pipeline/queries/cross/cacem/themes_regulatory_areas.sql deleted file mode 100644 index 785593ccfc..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/themes_regulatory_areas.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - themes_id, - regulatory_areas_id -FROM prod.themes_regulatory_areas -INNER JOIN prod."REG_ENV_V3" regulatory_areas ON regulatory_areas_id = regulatory_areas.id -WHERE regulatory_areas.geom IS NOT NULL - AND regulatory_areas.ent_name IS NOT NULL - AND regulatory_areas.layer_name IS NOT NULL -ORDER BY themes_id \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cacem/three_hundred_meters_areas.sql b/datascience/src/pipeline/queries/cross/cacem/three_hundred_meters_areas.sql deleted file mode 100644 index 461fcf1456..0000000000 --- a/datascience/src/pipeline/queries/cross/cacem/three_hundred_meters_areas.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT id, geom, secteur -FROM prod."300_metres"; diff --git a/datascience/src/pipeline/queries/cross/cnsp/12_miles_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/12_miles_areas.sql deleted file mode 100644 index 4638794b6f..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/12_miles_areas.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - ogc_fid, - wkb_geometry, - nature, - type, - descriptio, - reference, - beginlifes, - territory, - country, - agency, - inspireid -FROM prod."12_miles_areas" \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/3_miles_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/3_miles_areas.sql deleted file mode 100644 index 5135de78d9..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/3_miles_areas.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - ogc_fid, - wkb_geometry, - nature, - type, - descriptio, - beginlifes, - territory, - country, - agency, - inspireid -FROM prod."3_miles_areas" \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/6_miles_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/6_miles_areas.sql deleted file mode 100644 index 714f56c0c4..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/6_miles_areas.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - ogc_fid, - wkb_geometry, - nature, - type, - descriptio, - reference, - beginlifes, - territory, - country, - agency, - neighbor, - inspireid -FROM prod."6_miles_areas" \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/aem_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/aem_areas.sql deleted file mode 100644 index 3cab064413..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/aem_areas.sql +++ /dev/null @@ -1,17 +0,0 @@ -SELECT - ogc_fid, - wkb_geometry, - name, - descriptio, - "timestamp", - begin, - "end", - altitudemo, - tessellate, - extrude, - visibility, - draworder, - icon, - layer, - path -FROM prod.aem_areas \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/departments_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/departments_areas.sql deleted file mode 100644 index b4e6e60704..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/departments_areas.sql +++ /dev/null @@ -1,18 +0,0 @@ -SELECT - dep.insee_dep, - dep.nom AS name, - ST_Multi( - ST_MakeValid( - ST_Simplify( - CASE - WHEN dep_mer.geom IS NULL THEN dep.geom - ELSE ST_Union(dep.geom, dep_mer.geom) - END, - 0.0001 - ) - ) - ) AS geometry -FROM prod.departements dep -LEFT JOIN prod.departements_en_mer_metropole dep_mer -ON dep.id = dep_mer.id -ORDER BY insee_dep \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/eez_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/eez_areas.sql deleted file mode 100644 index d135badb9a..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/eez_areas.sql +++ /dev/null @@ -1,34 +0,0 @@ -SELECT - ogc_fid, - wkb_geometry, - "union", - mrgid_eez, - territory1, - mrgid_ter1, - iso_ter1, - un_ter1, - sovereign1, - mrgid_sov1, - iso_sov1, - un_sov1, - territory2, - mrgid_ter2, - iso_ter2, - un_ter2, - sovereign2, - mrgid_sov2, - iso_sov2, - un_sov2, - territory3, - mrgid_ter3, - iso_ter3, - un_ter3, - sovereign3, - mrgid_sov3, - iso_sov3, - un_sov3, - pol_type, - y_1, - x_1, - area_km2 -FROM prod.eez_areas \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/facade_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/facade_areas.sql deleted file mode 100644 index 025955903d..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/facade_areas.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - facade_cacem AS facade, - st_multi(ST_SubDivide(geometry)) AS geometry -FROM prod.facade_areas \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/facade_areas_unextended.sql b/datascience/src/pipeline/queries/cross/cnsp/facade_areas_unextended.sql deleted file mode 100644 index ea68bbae1d..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/facade_areas_unextended.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT id, geom, facade - FROM prod.facade_areas_unextended \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/saltwater_limit_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/saltwater_limit_areas.sql deleted file mode 100644 index 65ba1d8c8f..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/saltwater_limit_areas.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - id, - geom, - objnam -FROM prod.saltwater_limit_areas \ No newline at end of file diff --git a/datascience/src/pipeline/queries/cross/cnsp/transversal_sea_limit_areas.sql b/datascience/src/pipeline/queries/cross/cnsp/transversal_sea_limit_areas.sql deleted file mode 100644 index 9ef8bd1563..0000000000 --- a/datascience/src/pipeline/queries/cross/cnsp/transversal_sea_limit_areas.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - id, - geom, - objnam -FROM prod.transversal_sea_limit_areas \ No newline at end of file diff --git a/datascience/src/pipeline/queries/fmc/control_units.sql b/datascience/src/pipeline/queries/fmc/control_units.sql deleted file mode 100644 index e70a1412d7..0000000000 --- a/datascience/src/pipeline/queries/fmc/control_units.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - idc_fmc_moyen_controle AS id, - -- Handle duplicated administration with ids 1010 and 7 in integration - CASE WHEN idc_fmc_administration = 1010 THEN 7 ELSE idc_fmc_administration END AS administration_id, - INITCAP(libelle) AS name -FROM FMC2.FMC_CODE_MOYEN_CONTROLE \ No newline at end of file diff --git a/datascience/src/pipeline/queries/fmc/historic_controls.sql b/datascience/src/pipeline/queries/fmc/historic_controls.sql deleted file mode 100644 index c86d8470b7..0000000000 --- a/datascience/src/pipeline/queries/fmc/historic_controls.sql +++ /dev/null @@ -1,71 +0,0 @@ -WITH control_actions as ( - SELECT - rc.id_fmc_bc_resultat_ctrl_env as id, - rc.ID_FMC_BC_MISSION as mission_id, - COALESCE(rc.NBRE_PERS_NAV_CTRLES, 1) as action_number_of_controls, - rc.DATE_DEBUT as action_start_datetime_utc, - 'CONTROL' as action_type - FROM FMC2.FMC_BC_RESULTAT_CTRL_ENV rc - WHERE rc.DATE_DEBUT < TO_DATE( '2023-01-01', 'YYYY-MM-DD') -), - -actions_infractions AS ( - SELECT - rc.id_fmc_bc_resultat_ctrl_env as id, - LISTAGG( - COALESCE( - TRIM(nat.CODE), - REGEXP_SUBSTR(inf.LIBELLE_AUTRE_INFRACTION, '\d{4,5}') - ), - ',' - ) WITHIN GROUP (ORDER BY 1) as natinf - FROM FMC2.FMC_BC_RESULTAT_CTRL_ENV rc - JOIN FMC2.FMC_BC_RES_CTRL_ENV_INFR inf - ON inf.ID_FMC_BC_RESULTAT_CTRL_ENV=rc.ID_FMC_BC_RESULTAT_CTRL_ENV - LEFT JOIN FMC2.FMC_CODE_NATINF nat - ON inf.IDC_FMC_NATINF=nat.IDC_FMC_NATINF - WHERE nat.CODE IS NOT NULL OR REGEXP_SUBSTR(inf.LIBELLE_AUTRE_INFRACTION, '\d{4,5}') IS NOT NULL - GROUP BY rc.id_fmc_bc_resultat_ctrl_env -), - -actions_themes AS ( - SELECT - rc.id_fmc_bc_resultat_ctrl_env as id, - LISTAGG(cnc.LIBELLE, '') WITHIN GROUP (ORDER BY 1) as themes - FROM FMC2.FMC_BC_RESULTAT_CTRL_ENV rc - JOIN FMC2.FMC_BC_RES_CTRL_ENV_NAT rcn - ON rc.ID_FMC_BC_RESULTAT_CTRL_ENV=rcn.ID_FMC_BC_RESULTAT_CTRL_ENV - JOIN FMC2.FMC_CODE_NATURE_CONTROLE cnc - ON cnc.IDC_FMC_NATURE_CONTROLE=rcn.IDC_FMC_NATURE_CONTROLE - GROUP BY rc.id_fmc_bc_resultat_ctrl_env -), - -actions_protected_species AS ( - SELECT - rc.id_fmc_bc_resultat_ctrl_env as id, - LISTAGG(cep.LIBELLE, ',') WITHIN GROUP (ORDER BY 1) as protected_species - FROM FMC2.FMC_BC_RESULTAT_CTRL_ENV rc - JOIN FMC2.FMC_BC_ESPECE_PROTEGEE ep - ON ep.ID_FMC_BC_PERTURBATION=rc.ID_FMC_BC_PERTURBATION - LEFT JOIN FMC2.FMC_CODE_ESPECE_PROTEGEE cep - ON ep.IDC_FMC_ESPECE_PROTEGEE=cep.IDC_FMC_ESPECE_PROTEGEE - GROUP BY rc.id_fmc_bc_resultat_ctrl_env -) - -SELECT - act.id, - thm.themes, - act.action_number_of_controls, - inf.natinf, - psp.protected_species, - act.mission_id, - act.action_start_datetime_utc, - act.action_type -FROM control_actions act -LEFT JOIN actions_infractions inf -ON inf.id = act.id -LEFT JOIN actions_themes thm -ON thm.id = act.id -LEFT JOIN actions_protected_species psp -ON psp.id = act.id -order by id diff --git a/datascience/src/pipeline/queries/fmc/historic_missions.sql b/datascience/src/pipeline/queries/fmc/historic_missions.sql deleted file mode 100644 index 86bb356efb..0000000000 --- a/datascience/src/pipeline/queries/fmc/historic_missions.sql +++ /dev/null @@ -1,20 +0,0 @@ -SELECT - m.ID_FMC_BC_MISSION AS id, - CASE - WHEN typ.LIBELLE_COURT='Au port' THEN 'LAND' - WHEN typ.LIBELLE_COURT='En mer' THEN 'SEA' - WHEN typ.LIBELLE_COURT='Aérien' THEN 'AIR' - END AS mission_type, - m.TRIGRAMME_UTILISATEUR_SAISIE AS open_by, - m.OBSERVATIONS as observations_cacem, - fac.LIBELLE as facade, - m.DATE_DEBUT_MISSION as start_datetime_utc, - m.DATE_FIN_MISSION as end_datetime_utc, - m.TRIGRAMME_UTILISATEUR_CLOTURE AS closed_by, - 'POSEIDON_CACEM' AS mission_source -FROM FMC2.FMC_BC_MISSION m -LEFT JOIN FMC2.FMC_CODE_TYPE_CONTROLE typ -ON m.IDC_FMC_TYPE_MISSION = typ.IDC_FMC_TYPE_CONTROLE -LEFT JOIN FMC2.FMC_CODE_PLAN_FACADE fac -ON m.IDC_FMC_PLAN_FACADE = fac.IDC_FMC_PLAN_FACADE -WHERE m.DATE_DEBUT_MISSION < TO_DATE( '2023-01-01', 'YYYY-MM-DD') \ No newline at end of file diff --git a/datascience/src/pipeline/queries/fmc/historic_missions_units.sql b/datascience/src/pipeline/queries/fmc/historic_missions_units.sql deleted file mode 100644 index b5b64b788c..0000000000 --- a/datascience/src/pipeline/queries/fmc/historic_missions_units.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - m.ID_FMC_BC_MISSION AS mission_id, - mu.IDC_FMC_MOYEN_CONTROLE AS control_unit_id -FROM FMC2.FMC_BC_MISSION m -JOIN FMC2.FMC_BC_MISSION_UNITE mu -ON m.ID_FMC_BC_MISSION = mu.ID_FMC_BC_MISSION -WHERE m.DATE_DEBUT_MISSION < TO_DATE( '2023-01-01', 'YYYY-MM-DD') diff --git a/datascience/src/pipeline/queries/fmc/natinf.sql b/datascience/src/pipeline/queries/fmc/natinf.sql deleted file mode 100644 index d942d21443..0000000000 --- a/datascience/src/pipeline/queries/fmc/natinf.sql +++ /dev/null @@ -1,22 +0,0 @@ -WITH t1 AS ( - SELECT - CAST(TRIM(BOTH ' ' FROM inf.code) AS INTEGER) as natinf_code, - inf.texte_reglementaire as regulation, - typinf.libelle as infraction_category, - inf.libelle as infraction, - ROW_NUMBER() OVER (PARTITION BY TRIM(BOTH ' ' FROM inf.code) ORDER BY inf.texte_reglementaire) AS row_num - FROM FMC2.FMC_CODE_NATINF inf - LEFT JOIN FMC2.FMC_CODE_TYPE_INFRACTION typinf - ON inf.idc_fmc_type_infraction = typinf.idc_fmc_type_infraction - WHERE - inf.bloque = 0 AND - REGEXP_LIKE(TRIM(BOTH ' ' FROM inf.code), '^[0-9]+$') -- exclude natinfs that are not castable to integers -) - -SELECT - natinf_code, - regulation, - infraction_category, - infraction -FROM t1 -WHERE row_num=1 \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/all_control_units.sql b/datascience/src/pipeline/queries/monitorenv/all_control_units.sql deleted file mode 100644 index 74aa39abbb..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/all_control_units.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - cu.id AS control_unit_id, - cu.name AS control_unit_name, - ARRAY_AGG(DISTINCT email) AS email_addresses -FROM control_units cu -JOIN control_unit_contacts cuc -ON cu.id = cuc.control_unit_id -WHERE - email IS NOT NULL - AND is_email_subscription_contact - AND NOT archived -GROUP BY 1, 2 -ORDER BY 1, 2 diff --git a/datascience/src/pipeline/queries/monitorenv/amp_hashes.sql b/datascience/src/pipeline/queries/monitorenv/amp_hashes.sql deleted file mode 100644 index e03b02b5c1..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/amp_hashes.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - id, - row_hash AS monitorenv_row_hash -FROM public.amp_cacem \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/control_units.sql b/datascience/src/pipeline/queries/monitorenv/control_units.sql deleted file mode 100644 index d48b47ed81..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/control_units.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - cu.id AS control_unit_id, - cu.name AS control_unit_name, - ARRAY_AGG(DISTINCT email) AS email_addresses -FROM control_units cu -JOIN control_unit_contacts cuc -ON cu.id = cuc.control_unit_id -WHERE - email IS NOT NULL - AND is_email_subscription_contact - AND NOT archived - AND cu.id IN :control_unit_ids -GROUP BY 1, 2 -ORDER BY 1, 2 diff --git a/datascience/src/pipeline/queries/monitorenv/env_actions_to_email.sql b/datascience/src/pipeline/queries/monitorenv/env_actions_to_email.sql deleted file mode 100644 index ccf3233ef2..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/env_actions_to_email.sql +++ /dev/null @@ -1,95 +0,0 @@ -WITH actions_to_export AS ( - SELECT DISTINCT ON (id, control_unit_id) - id, - action_start_datetime_utc, - action_end_datetime_utc, - mission_type, - action_type, - control_unit_id, - action_facade, - action_department, - infraction, - number_of_controls, - surveillance_duration, - CASE - WHEN action_type = 'SURVEILLANCE' AND action_end_datetime_utc < :min_datetime_utc THEN true - WHEN action_type = 'CONTROL' AND action_start_datetime_utc < :min_datetime_utc THEN true - ELSE false - END AS is_late_update - FROM analytics_actions - WHERE ( - action_type = 'SURVEILLANCE' - AND ( - ( - action_end_datetime_utc >= :min_datetime_utc AND - action_end_datetime_utc < :max_datetime_utc - ) OR ( - action_end_datetime_utc < :min_datetime_utc AND - mission_updated_at_utc >= :min_datetime_utc AND - mission_updated_at_utc < :max_datetime_utc - ) - ) - ) OR ( - action_type = 'CONTROL' - AND ( - ( - action_start_datetime_utc >= :min_datetime_utc AND - action_start_datetime_utc < :max_datetime_utc - ) OR ( - action_start_datetime_utc < :min_datetime_utc AND - mission_updated_at_utc >= :min_datetime_utc AND - mission_updated_at_utc < :max_datetime_utc - ) - ) - ) -), - --- Controls may have more than one position so we take the average -controls_average_positions AS ( - SELECT - id, - AVG(latitude) AS latitude, - AVG(longitude) AS longitude - FROM analytics_actions - WHERE - action_type = 'CONTROL' - AND id IN (SELECT id FROM actions_to_export) - AND latitude IS NOT NULL - AND longitude IS NOT NULL - GROUP BY id -), - -actions_unique_themes_and_subthemes AS ( - SELECT DISTINCT - id, - theme_level_1, - theme_level_2 - FROM analytics_actions - WHERE id IN (SELECT id FROM actions_to_export) -), - -action_themes AS ( - SELECT DISTINCT - id, - jsonb_build_object( - 'theme_level_1', - theme_level_1, - 'themes_level_2', - jsonb_agg(theme_level_2) OVER (PARTITION BY id, theme_level_1)) AS theme - FROM actions_unique_themes_and_subthemes -), - -action_themes_and_subthemes AS ( - SELECT - id, - jsonb_object_agg(theme->>'theme_level_1', theme->'themes_level_2') AS themes - FROM action_themes - GROUP BY id -) - -SELECT a.*, t.themes, p.longitude, p.latitude -FROM actions_to_export a -LEFT JOIN action_themes_and_subthemes t -ON a.id = t.id -LEFT JOIN controls_average_positions p -ON a.id = p.id \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/regulations_hashes.sql b/datascience/src/pipeline/queries/monitorenv/regulations_hashes.sql deleted file mode 100644 index 429111d51c..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/regulations_hashes.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - id, - row_hash AS monitorenv_row_hash -FROM public.regulations_cacem \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/remove_broken_missions_resources_links.sql b/datascience/src/pipeline/queries/monitorenv/remove_broken_missions_resources_links.sql deleted file mode 100644 index 32f65cf794..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/remove_broken_missions_resources_links.sql +++ /dev/null @@ -1,2 +0,0 @@ -DELETE FROM missions_control_resources -WHERE control_resource_id NOT IN (SELECT id FROM control_unit_resources) \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/tags_hashes.sql b/datascience/src/pipeline/queries/monitorenv/tags_hashes.sql deleted file mode 100644 index 95b597f647..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/tags_hashes.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - id, - md5( - COALESCE("name"::VARCHAR, '') || - COALESCE(parent_id::INT, 0) || - COALESCE(started_at::TIMESTAMP, NOW()) || - COALESCE(ended_at::TIMESTAMP, NOW()) - ) AS monitorenv_row_hash -FROM public.tags \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/themes_hashes.sql b/datascience/src/pipeline/queries/monitorenv/themes_hashes.sql deleted file mode 100644 index 2cfca6a1eb..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/themes_hashes.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - id, - md5( - COALESCE("name"::VARCHAR, '') || - COALESCE(parent_id::INT, 0) || - COALESCE(started_at::TIMESTAMP, NOW()) || - COALESCE(ended_at::TIMESTAMP, NOW()) || - COALESCE(control_plan_themes_id::INT, 0) || - COALESCE(control_plan_sub_themes_id::INT, 0) || - COALESCE(control_plan_tags_id::INT, 0) || - COALESCE(reportings_control_plan_sub_themes_id::INT, 0) - ) AS monitorenv_row_hash -FROM public.themes \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/update_actions_departments.sql b/datascience/src/pipeline/queries/monitorenv/update_actions_departments.sql deleted file mode 100644 index 36984bbb4c..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/update_actions_departments.sql +++ /dev/null @@ -1,40 +0,0 @@ -WITH departments_intersection_areas AS ( - SELECT - env_actions.id, - departments_areas.insee_dep, - SUM(ST_Area(ST_Intersection(ST_MakeValid(env_actions.geom), departments_areas.geometry)::geography)) AS intersection_area - FROM env_actions - JOIN missions - ON missions.id = env_actions.mission_id - LEFT JOIN departments_areas - ON ST_Intersects( - ST_MakeValid( - CASE WHEN env_actions.action_type = 'SURVEILLANCE' AND env_actions.value->>'cover_mission_zone' = 'true' THEN COALESCE(missions.geom, env_actions.geom) - ELSE COALESCE(env_actions.geom, missions.geom) END - ), - departments_areas.geometry - ) - WHERE missions.mission_source IN ('MONITORENV', 'MONITORFISH') - GROUP BY env_actions.id, departments_areas.insee_dep -), - -ranked_departments_intersection_areas AS ( - SELECT - id, - insee_dep, - RANK() OVER (PARTITION BY id ORDER BY intersection_area DESC) AS rk - FROM departments_intersection_areas -), - -env_actions_departments AS ( - SELECT - id, - insee_dep - FROM ranked_departments_intersection_areas - WHERE rk = 1 -) - -UPDATE env_actions -SET department = env_actions_departments.insee_dep -FROM env_actions_departments -WHERE env_actions.id = env_actions_departments.id; \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/update_actions_facades.sql b/datascience/src/pipeline/queries/monitorenv/update_actions_facades.sql deleted file mode 100644 index cd5dc493c4..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/update_actions_facades.sql +++ /dev/null @@ -1,40 +0,0 @@ -WITH facades_intersection_areas AS ( - SELECT - env_actions.id, - facade_areas_subdivided.facade, - SUM(ST_Area(ST_Intersection(ST_MakeValid(env_actions.geom), facade_areas_subdivided.geometry)::geography)) AS intersection_area - FROM env_actions - JOIN missions - ON missions.id = env_actions.mission_id - LEFT JOIN facade_areas_subdivided - ON ST_Intersects( - ST_MakeValid( - CASE WHEN env_actions.action_type = 'SURVEILLANCE' AND env_actions.value->>'cover_mission_zone' = 'true' THEN COALESCE(missions.geom, env_actions.geom) - ELSE COALESCE(env_actions.geom, missions.geom) END - ), - facade_areas_subdivided.geometry - ) - WHERE missions.mission_source IN ('MONITORENV', 'MONITORFISH') - GROUP BY env_actions.id, facade_areas_subdivided.facade -), - -ranked_facades_intersection_areas AS ( - SELECT - id, - facade, - RANK() OVER (PARTITION BY id ORDER BY intersection_area DESC) AS rk - FROM facades_intersection_areas -), - -env_actions_facades AS ( - SELECT - id, - facade - FROM ranked_facades_intersection_areas - WHERE rk = 1 -) - -UPDATE env_actions -SET facade = env_actions_facades.facade -FROM env_actions_facades -WHERE env_actions.id = env_actions_facades.id; \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorenv/update_missions_facades.sql b/datascience/src/pipeline/queries/monitorenv/update_missions_facades.sql deleted file mode 100644 index ba7f7b692b..0000000000 --- a/datascience/src/pipeline/queries/monitorenv/update_missions_facades.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH facades_intersection_areas AS ( - SELECT - missions.id, - facade_areas_subdivided.facade, - SUM(ST_Area(ST_Intersection(ST_MakeValid(missions.geom), facade_areas_subdivided.geometry)::geography)) AS intersection_area - FROM missions - LEFT JOIN facade_areas_subdivided - ON ST_Intersects(ST_MakeValid(missions.geom), facade_areas_subdivided.geometry) - WHERE mission_source IN ('MONITORENV', 'MONITORFISH') - GROUP BY missions.id, facade_areas_subdivided.facade -), - -ranked_facades_intersection_areas AS ( - SELECT - id, - facade, - RANK() OVER (PARTITION BY id ORDER BY intersection_area DESC) AS rk - FROM facades_intersection_areas -), - -missions_facades AS ( - SELECT - id, - facade - FROM ranked_facades_intersection_areas - WHERE rk = 1 -) - -UPDATE missions m -SET facade = mf.facade -FROM missions_facades mf -WHERE m.id = mf.id; \ No newline at end of file diff --git a/datascience/src/pipeline/queries/monitorfish/fao_areas.sql b/datascience/src/pipeline/queries/monitorfish/fao_areas.sql deleted file mode 100644 index bd31e69e00..0000000000 --- a/datascience/src/pipeline/queries/monitorfish/fao_areas.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - f_code, - wkb_geometry as geometry -FROM fao_areas \ No newline at end of file diff --git a/datascience/src/pipeline/shared_tasks/__init__.py b/datascience/src/pipeline/shared_tasks/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/pipeline/shared_tasks/control_flow.py b/datascience/src/pipeline/shared_tasks/control_flow.py deleted file mode 100644 index 6e59b47ecc..0000000000 --- a/datascience/src/pipeline/shared_tasks/control_flow.py +++ /dev/null @@ -1,109 +0,0 @@ -from pathlib import Path -from typing import List - -import prefect -from prefect import task -from prefect.engine.signals import SKIP -from prefect.utilities.graphql import with_args - - -@task(checkpoint=False) -def check_flow_not_running() -> bool: - """ - This task determines if the flow in which it runs has flow runs in a 'running' - state. This may be used to stop a flow run from proceeding if the flow's previous - run has not yet finished running and concurrency is not desired. - - Returns: - bool: ``True`` if the flow does NOT already have flow runs in a "running" - state, ``False`` if it does - """ - - prefect_client = prefect.Client() - - query = { - "query": { - with_args( - "flow_run", - { - "where": { - "flow_id": {"_eq": prefect.context.flow_id}, - "id": {"_neq": prefect.context.flow_run_id}, - "state": {"_eq": "Running"}, - } - }, - ): {"id"} - } - } - response = prefect_client.graphql(query) - active_flow_runs = response.get("data", {}).get("flow_run", []) - - flow_is_not_running = active_flow_runs == [] - - if not flow_is_not_running: - logger = prefect.context.get("logger") - logger.info("This flow already has runs in a 'Running' state.") - - return flow_is_not_running - - -@task(checkpoint=False) -def str_to_path(path: str) -> Path: - """Returns `Path` object corresponding to input `str` - - Args: - path (str): 'stairway/to/heaven' - - Returns: - Path: Path('stairway/to/heaven') - """ - return Path(path) - - -@task( - checkpoint=False, - skip_on_upstream_skip=False, - trigger=prefect.triggers.all_finished, -) -def filter_results(task_results) -> List: - """ - Filters invalid results from an input mapped results list. - - Warning: It is mandatory to use `skip_on_upstream_skip = False`, otherwise the whole - task is skipped if any of the results of the upstream mapped task raises a SKIP - signal, which defeats the purpose of this task which is to discard the invalid - results produced by the upstream tasks and keep the valid ones and pass them on - downstream. - - As a consequence, - - !!!! THIS TASK WILL RUN EVEN IF THE UPSTREAM TASKS ARE SKIPPED !!!! - - This can have weird consequences, as the task can run successfully, and pass on - successful results to downstream tasks which were also supposed to be skipped. - - This happens including in a `case` branch of a flow that should be skipped. - - In order to try to circumvent this issue, we raise a `SKIP` signal if the input is - `None`, which is what happens if the input is provided by an upsteam task that is - itself skipped. This does not cover all cases at all, in particular, in situations - where tasks may be skipped (in case branch mostly) make sure not to use this task - with input data coming from upstream tasks that may not be skipped. - - Args: - task_results: List of (mapped) task results. - - Raises: - SKIP: If input is `None` - - Returns: - List: Filtered list with SKIPs, Nones and Errors removed - """ - if isinstance(task_results, list): - return [ - r - for r in task_results - if not isinstance(r, (BaseException, SKIP, type(None))) - ] - elif task_results is None: - raise SKIP diff --git a/datascience/src/pipeline/shared_tasks/datagouv.py b/datascience/src/pipeline/shared_tasks/datagouv.py deleted file mode 100644 index 4411e416ac..0000000000 --- a/datascience/src/pipeline/shared_tasks/datagouv.py +++ /dev/null @@ -1,162 +0,0 @@ -from io import BytesIO - -import geopandas as gpd -import pandas as pd -import prefect -import requests -from prefect import task - -from config import DATAGOUV_API_ENDPOINT, DATAGOUV_API_KEY, PROXIES, ROOT_DIRECTORY -from src.pipeline.utils import remove_file - -HEADERS = { - "X-API-KEY": DATAGOUV_API_KEY, -} - - -def api_url(path: str): - return DATAGOUV_API_ENDPOINT + path - - -@task(checkpoint=False) -def update_resource( - dataset_id: str, - resource_id: str, - resource_title: str, - resource: BytesIO, - mock_update: bool, -): - """ - Update the designated resource on data.gouv.fr - - Args: - dataset_id (str): dataset id to update on data.gouv.fr - resource_id (str): resource id to update on data.gouv.fr - resource_title (str): title of the resource file after upload - resource (BytesIO): resource to upload - mock_update (bool): if ``True``, the post request is not actually sent - - Returns: - requests.Response: Response object - """ - logger = prefect.context.get("logger") - - url = api_url(f"/datasets/{dataset_id}/resources/{resource_id}/upload/") - - if not mock_update: - logger.info(f"Updating resource on {url}") - response = requests.post( - url, - files={"file": (resource_title, resource)}, - headers=HEADERS, - proxies=PROXIES, - ) - response.raise_for_status() - - else: - logger.info(f"Mocking update on resource on {url}") - response = requests.Response() - response.url = url - response.status_code = 200 - return response - - -@task(checkpoint=False) -def get_csv_file_object(df: pd.DataFrame) -> BytesIO: - """ - Returns a `BytesIO` csv file object from the input `DataFrame`. - Useful to upload a `DataFrame` to data.gouv.fr - - The index is not included in the output csv file. - - Args: - df (pd.DataFrame): DataFrame to convert - - Returns: - BytesIO: file object - - Examples: - import pandas as pd - >>> df = pd.DataFrame({"a": [10, 20, 30], "b": [40, 50, 60]}) - >>> df - a b - 0 10 40 - 1 20 50 - 2 30 60 - >>> buf = df_to_csv_file_object.run(df) - >>> pd.read_csv(buf) - a b - 0 10 40 - 1 20 50 - 2 30 60 - """ - buf = BytesIO() - df.to_csv(buf, mode="wb", index=False) - buf.seek(0) - return buf - - -@task(checkpoint=False) -def get_geopackage_file_object(gdf: gpd.GeoDataFrame, layers: str = None) -> BytesIO: - """ - Returns a `BytesIO` geopackage file object. from the input `GeoDataFrame`. - Useful to upload a `GeoDataFrame` to data.gouv.fr - - If `layers` is given, the geopackage will be organized in layers according to the - data labels of the `layers` column. If there are null values in the `layers` column, - the corresponding rows will not be included in the geopackage. - - Args: - gdf (gpd.DataFrame): GeoDataFrame to convert - layers (str, optional): name of the column to use as layer labels in the - geopackage. Defaults to None. - - Returns: - BytesIO: file object - - Examples: - import geopandas as gpd - from shapely.geometry import Point - >>> gdf = gpd.GeoDataFrame({ - "a": [10, 20, 30], - "geometry": [Point(1, 2), Point(3, 4), Point(5, 6)] - }) - >>> gdf - a geometry - 0 10 POINT (1.00000 2.00000) - 1 20 POINT (3.00000 4.00000) - 2 30 POINT (5.00000 6.00000) - >>> buf = get_geopackage_file_object.run(gdf) - >>> gpd.read_file(buf, driver="GPKG") - a geometry - 0 10 POINT (1.00000 2.00000) - 1 20 POINT (3.00000 4.00000) - 2 30 POINT (5.00000 6.00000) - """ - - buf = BytesIO() - - if layers: - # Tried using tempfile.TemporaryFile without success. - # Try again at your own risk :) - temp_file_path = ROOT_DIRECTORY / "src/pipeline/data/tmp_geopackage.gpkg" - remove_file(temp_file_path, ignore_errors=True) - - try: - for layer in gdf[layers].dropna().unique(): - - gdf[gdf[layers] == layer].to_file( - temp_file_path, driver="GPKG", layer=layer - ) - - with open(temp_file_path, "rb") as f: - buf.write(f.read()) - - finally: - remove_file(temp_file_path, ignore_errors=True) - - else: - gdf.to_file(buf, driver="GPKG") - - buf.seek(0) - return buf diff --git a/datascience/src/pipeline/shared_tasks/dates.py b/datascience/src/pipeline/shared_tasks/dates.py deleted file mode 100644 index c64ddb349a..0000000000 --- a/datascience/src/pipeline/shared_tasks/dates.py +++ /dev/null @@ -1,49 +0,0 @@ -from datetime import datetime, timedelta -from typing import List - -from prefect import task - -from src.pipeline.helpers import dates - - -@task(checkpoint=False) -def get_utcnow(): - """Task version of `datetime.utcnow`""" - return datetime.utcnow() - - -@task(checkpoint=False) -def make_timedelta(**kwargs) -> timedelta: - """Task version of `datetime.timedelta`""" - return timedelta(**kwargs) - - -@task(checkpoint=False) -def make_periods( - start_hours_ago: int, - end_hours_ago: int, - minutes_per_chunk: int, - chunk_overlap_minutes: int, -) -> List[dates.Period]: - """ - `prefect.Task` version of the function `src.pipeline.helpers.dates.make_periods`, - with the difference that start and end dates are to be given as a number of hours - from the current date (instead of `datetime` objects), and chunk duration and - overlap are to be given as a number of minutes (instead of `timedelta` objects). - This is to accomodate for the fact that Prefect flows' parameters must be - JSON-serializable, and `datetime` and `timedelta` are not, by default. - - `src.pipeline.helpers.dates.make_periods` is recursive, hence the construction as a - python function first and not directly as Prefect `Task`. - - See `src.pipeline.helpers.dates.make_periods` for help. - """ - - now = datetime.utcnow() - - return dates.make_periods( - start_datetime_utc=now - timedelta(hours=start_hours_ago), - end_datetime_utc=now - timedelta(hours=end_hours_ago), - period_duration=timedelta(minutes=minutes_per_chunk), - overlap=timedelta(minutes=chunk_overlap_minutes), - ) diff --git a/datascience/src/pipeline/shared_tasks/etl.py b/datascience/src/pipeline/shared_tasks/etl.py deleted file mode 100644 index 3c4c104422..0000000000 --- a/datascience/src/pipeline/shared_tasks/etl.py +++ /dev/null @@ -1,38 +0,0 @@ -from pathlib import Path - -import pandas as pd -import prefect -from prefect import task -from sqlalchemy import text - -from config import LIBRARY_LOCATION -from src.db_config import create_engine - - -@task(checkpoint=False) -def run_sql_script(sql_filepath: Path) -> pd.DataFrame: - - logger = prefect.context.get("logger") - with open(sql_filepath, "r") as sql_file: - query = text(sql_file.read()) - - e = create_engine("monitorenv_remote") - - logger.info(f"Executing {sql_filepath}.") - with e.begin() as con: - con.execute(query) - - -@task(checkpoint=False) -def extract_csv_file(file_name: str) -> pd.DataFrame: - """ - Returns a CSV file's content as DataFrame. - The designated file must be in the folder pipeline/data. - - Args: - file_name (str): Name of the file - - Returns: - pd.DataFrame: CSV file content - """ - return pd.read_csv(LIBRARY_LOCATION / f"pipeline/data/{file_name}") diff --git a/datascience/src/pipeline/shared_tasks/infrastructure.py b/datascience/src/pipeline/shared_tasks/infrastructure.py deleted file mode 100644 index d7d83c9606..0000000000 --- a/datascience/src/pipeline/shared_tasks/infrastructure.py +++ /dev/null @@ -1,35 +0,0 @@ -import prefect -from prefect import task -from sqlalchemy import Table - -from src.db_config import create_engine -from src.pipeline import utils - - -@task(checkpoint=False) -def get_table( - table_name: str, - schema: str = "public", - database: str = "monitorenv_remote", -) -> Table: - """ - Returns a `Table` representing the specified table. - - Args: - table_name (str): Name of the table - schema (str, optional): Schema of the table. Defaults to "public". - database (str, optional): Database of the table, can be 'monitorenv_remote' - or 'monitorfish_local'. Defaults to "monitorenv_remote". - - Returns: - Table: `sqlalchemy.Table` representing the specified table. - """ - - logger = prefect.context.get("logger") - - return utils.get_table( - table_name, - schema=schema, - conn=create_engine(database), - logger=logger, - ) diff --git a/datascience/src/pipeline/shared_tasks/update_queries.py b/datascience/src/pipeline/shared_tasks/update_queries.py deleted file mode 100644 index 087435e7a1..0000000000 --- a/datascience/src/pipeline/shared_tasks/update_queries.py +++ /dev/null @@ -1,67 +0,0 @@ - -import pandas as pd -from prefect import task -import prefect - - -@task(checkpoint=False) -def update_required(ids_to_update: set) -> bool: - logger = prefect.context.get("logger") - n = len(ids_to_update) - if n > 0: - logger.info(f"Found {n} row(s) to update.") - res = True - else: - logger.info("No row update was found.") - res = False - return res - -@task(checkpoint=False) -def merge_hashes( - local_hashes: pd.DataFrame, remote_hashes: pd.DataFrame, how: str = "outer" -) -> pd.DataFrame: - return pd.merge(local_hashes, remote_hashes, on="id", how=how) - -@task(checkpoint=False) -def select_ids_to_update(hashes: pd.DataFrame) -> set: - ids_to_update = set( - hashes.loc[ - (hashes.cacem_row_hash.notnull()) - & (hashes.cacem_row_hash != hashes.monitorenv_row_hash), - "id", - ] - ) - - return ids_to_update - -@task(checkpoint=False) -def select_ids_to_delete(hashes: pd.DataFrame) -> set: - return set(hashes.loc[hashes.cacem_row_hash.isna(), "id"]) - -@task(checkpoint=False) -def select_ids_to_insert(hashes: pd.DataFrame) -> set: - return set(hashes.loc[hashes.monitorenv_row_hash.isna(), "id"]) - -@task(checkpoint=False) -def insert_required(ids_to_insert: set) -> bool: - logger = prefect.context.get("logger") - n = len(ids_to_insert) - if n > 0: - logger.info(f"Found {n} row(s) to add.") - res = True - else: - logger.info("No row to add was found.") - res = False - return res - -@prefect.task(checkpoint=False) -def delete_required(ids_to_delete: set) -> bool: - logger = prefect.context.get("logger") - n = len(ids_to_delete) - if n > 0: - logger.info(f"Found {n} row(s) to delete.") - res = True - else: - logger.info("No row to delete was found.") - res = False - return res diff --git a/datascience/src/pipeline/utils.py b/datascience/src/pipeline/utils.py deleted file mode 100644 index 0ab36cd6e3..0000000000 --- a/datascience/src/pipeline/utils.py +++ /dev/null @@ -1,165 +0,0 @@ -import csv -import logging -import os -import pathlib -import shutil -import sys -from io import StringIO -from typing import Sequence, Union - -import geoalchemy2 -import sqlalchemy -from sqlalchemy import MetaData, Table, func, select -from sqlalchemy.exc import InvalidRequestError - -# ***************************** Database operations utils ***************************** - - -def get_table( - table_name: str, - schema: str, - conn: sqlalchemy.engine.Connectable, - logger: logging.Logger, -) -> sqlalchemy.Table: - """Performs reflection to get a sqlalchemy Table object with metadata reflecting - the table found in the databse. Returns resulting Table object. - - If the table is not found in the database, raises an error. - """ - - meta = MetaData(schema=schema) - try: - geoalchemy2 - assert "geoalchemy2" in sys.modules - except (AssertionError, NameError): - logger.error( - "geoalchemy2 must be imported for geometry support in table reflection." - ) - raise - - try: - logger.info(f"Searching for table {schema}.{table_name}...") - meta.reflect(bind=conn, only=[table_name], views=True) - table = Table(table_name, meta, must_exist=True) - logger.info(f"Table {schema}.{table_name} found.") - except InvalidRequestError: - logger.error( - f"Table {schema}.{table_name} must exist. Make appropriate migrations " - + "and try again." - ) - raise - - return table - - -def delete( - table: sqlalchemy.Table, - connection: sqlalchemy.engine.base.Connection, - logger: logging.Logger, -): - """Deletes all rows from a table. - Useful to wipe a table before re-inserting fresh data in ETL jobs.""" - count_statement = select(func.count()).select_from(table) - n = connection.execute(count_statement).fetchall()[0][0] - if logger: - logger.info(f"Found existing table {table.name} with {n} rows.") - logger.info(f"Deleting table {table.name}...") - connection.execute(table.delete()) - count_statement = select(func.count()).select_from(table) - n = connection.execute(count_statement).fetchall()[0][0] - if logger: - logger.info(f"Rows after deletion: {n}.") - - -def delete_rows( - table: sqlalchemy.Table, - id_column: str, - ids_to_delete: Sequence, - connection: sqlalchemy.engine.base.Connection, - logger: logging.Logger, -): - """Deletes all rows of a table whose id is in ``ids_to_delete``. - - Args: - table (sqlalchemy.Table): table to remove rows from - id_column (str): name of the column in the table that contains ids to delete - ids (Sequence): list-like sequence of ids to look for in the table and delete - connection (sqlalchemy.engine.base.Connection): database connection - logger (logging.Logger): logger - - """ - count_statement = select(func.count()).select_from(table) - n = connection.execute(count_statement).fetchall()[0][0] - if logger: - logger.info(f"Found existing table {table.name} with {n} rows.") - logger.info(f"Deleting some rows from table {table.name}...") - - # to avoid certain type errors generated by psycopg2 - ids_to_delete = list(map(str, ids_to_delete)) - - connection.execute( - table.delete().where(table.c[id_column].in_(ids_to_delete)) - ) - - count_statement = select(func.count()).select_from(table) - n = connection.execute(count_statement).fetchall()[0][0] - if logger: - logger.info(f"Rows after deletion: {n}.") - - -def psql_insert_copy(table, conn, keys, data_iter): - """ - Execute SQL statement inserting data - - Parameters - ---------- - table : pandas.io.sql.SQLTable - conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection - keys : list of str - Column names - data_iter : Iterable that iterates the values to be inserted - """ - # gets a DBAPI connection that can provide a cursor - dbapi_conn = conn.connection - with dbapi_conn.cursor() as cur: - s_buf = StringIO() - writer = csv.writer(s_buf) - writer.writerows(data_iter) - s_buf.seek(0) - - columns = ", ".join('"{}"'.format(k) for k in keys) - if table.schema: - table_name = f'"{table.schema}"."{table.name}"' - else: - table_name = f'"{table.name}"' - - sql = "COPY {} ({}) FROM STDIN WITH CSV".format(table_name, columns) - cur.copy_expert(sql=sql, file=s_buf) - - -def move( - src_fp: pathlib.Path, dest_dirpath: pathlib.Path, if_exists: str = "raise" -) -> None: - """Moves a file to another directory. If the destination directory - does not exist, it is created, as well as all intermediate directories.""" - if not dest_dirpath.exists(): - os.makedirs(dest_dirpath) - try: - shutil.move(src_fp.as_posix(), dest_dirpath.as_posix()) - except shutil.Error: - if if_exists == "raise": - raise - elif if_exists == "replace": - os.remove(dest_dirpath / src_fp.name) - shutil.move(src_fp.as_posix(), dest_dirpath.as_posix()) - else: - raise ValueError( - f"if_exists must be 'raise' or 'replace', got {if_exists}" - ) - -def remove_file(fp: Union[str, pathlib.Path], ignore_errors: bool = True): - try: - os.remove(fp) - except Exception: - if not ignore_errors: - raise diff --git a/datascience/src/read_query.py b/datascience/src/read_query.py deleted file mode 100644 index a9524c2af6..0000000000 --- a/datascience/src/read_query.py +++ /dev/null @@ -1,165 +0,0 @@ -from pathlib import Path -from typing import Union - -import geopandas as gpd -import pandas as pd -from sqlalchemy import text - -from config import QUERIES_LOCATION - -from .db_config import create_engine - - -def read_saved_query( - db: str, - sql_filepath: Union[str, Path], - parse_dates: Union[list, dict, None] = None, - params: Union[None, dict] = None, - backend: str = "pandas", - geom_col: str = "geom", - crs: Union[int, None] = None, - **kwargs, -) -> pd.DataFrame: - """Run saved SQLquery on a database. Supported databases : - - 'ocan' : OCAN database - - 'fmc': FMC database - - 'monitorenv_remote': Monitorfish database - - 'monitorfish_local': Monitorfish PostGIS database hosted in CNSP - - 'cacem_local' : CACEM PostGIS database hosted in CNSP - - Database credentials must be present in the environement. - - Args: - db (str): Database name. Possible values : - 'ocan', 'fmc', 'monitorenv_remote', 'monitorfish_local' - sql_filepath (str): path to .sql file, starting from the saved queries folder. - example : "ocan/nav_fr_peche.sql" - parse_dates (Union[list, dict, None], optional): - - List of column names to parse as dates. - - Dict of ``{column_name: format string}`` where format string is - strftime compatible in case of parsing string times or is one of - (D, s, ns, ms, us) in case of parsing integer timestamps. - - Dict of ``{column_name: arg dict}``, where the arg dict corresponds - to the keyword arguments of :func:`pandas.to_datetime` - params (Union[dict, None], optional): Parameters to pass to execute method. - Defaults to None. - backend (str, optional) : 'pandas' to run a SQL query and return a - `pandas.DataFrame` or 'geopandas' to run a PostGIS query and return a - `geopandas.GeoDataFrame`. Defaults to 'pandas'. - geom_col (str, optional): column name to convert to shapely geometries when - `backend` is 'geopandas'. Ignored when `backend` is 'pandas'. Defaults to - 'geom'. - crs (Union[None, str], optional) : CRS to use for the returned GeoDataFrame; - if not set, tries to determine CRS from the SRID associated with the first - geometry in the database, and assigns that to all geometries. Ignored when - `backend` is 'pandas'. Defaults to None. - kwargs : passed to pd.read_sql or gpd.read_postgis - - Returns: - Union[pd.DataFrame, gpd.DataFrame]: Query results - """ - engine = create_engine(db=db) - sql_filepath = QUERIES_LOCATION / sql_filepath - with open(sql_filepath, "r") as sql_file: - query = text(sql_file.read()) - - if backend == "pandas": - return pd.read_sql( - query, engine, parse_dates=parse_dates, params=params, **kwargs - ) - - elif backend == "geopandas": - return gpd.read_postgis( - query, engine, geom_col=geom_col, crs=crs, params=params, **kwargs - ) - - else: - raise ValueError( - f"backend must be 'pandas' or 'geopandas', got {backend}" - ) - - -def read_query( - db: str, - query, - chunksize: Union[None, str] = None, - params: Union[dict, None] = None, - backend: str = "pandas", - geom_col: str = "geom", - crs: Union[int, None] = None, - **kwargs, -) -> Union[pd.DataFrame, gpd.GeoDataFrame]: - """Run SQLquery on a database. Supported databases : - - 'ocan' : OCAN database - - 'fmc': FMC database - - 'monitorenv_remote': Monitorfish database - - 'monitorfish_local': Monitorfish PostGIS database hosted in CNSP - - 'cacem_local' : CACEM PostGIS database hosted in CNSP - - Database credentials must be present in the environement. - - Args: - db (str): Database name. Possible values : - 'ocan', 'fmc', 'monitorenv_remote', 'monitorfish_local' - query (str): Query string or SQLAlchemy Selectable - chunksize (Union[None, str], optional): If specified, return an iterator where - `chunksize` is the number of rows to include in each chunk. Defaults to None. - params (Union[dict, None], optional): Parameters to pass to execute method. - Defaults to None. - backend (str, optional) : 'pandas' to run a SQL query and return a - `pandas.DataFrame` or 'geopandas' to run a PostGIS query and return a - `geopandas.GeoDataFrame`. Defaults to 'pandas'. - geom_col (str, optional): column name to convert to shapely geometries when - `backend` is 'geopandas'. Ignored when `backend` is 'pandas'. Defaults to - 'geom'. - crs (Union[None, str], optional) : CRS to use for the returned GeoDataFrame; - if not set, tries to determine CRS from the SRID associated with the first - geometry in the database, and assigns that to all geometries. Ignored when `backend` - is 'pandas'. Defaults to None. - kwargs : passed to pd.read_sql or gpd.read_postgis - - Returns: - Union[pd.DataFrame, gpd.DataFrame]: Query results - """ - - engine = create_engine(db=db, execution_options=dict(stream_results=True)) - - if backend == "pandas": - return pd.read_sql( - query, engine, chunksize=chunksize, params=params, **kwargs - ) - elif backend == "geopandas": - return gpd.read_postgis( - query, - engine, - geom_col=geom_col, - crs=crs, - chunksize=chunksize, - params=params, - **kwargs, - ) - else: - raise ValueError( - f"backend must be 'pandas' or 'geopandas', got {backend}" - ) - - -def read_table(db: str, schema: str, table_name: str): - """Loads database table into pandas Dataframe. Supported databases : - - 'ocan' : OCAN database - - 'fmc': FMC database - - 'monitorenv_remote': Monitorfish database - - 'monitorfish_local': Monitorfish PostGIS database hosted in CNSP - - 'cacem_local' : CACEM PostGIS database hosted in CNSP - - Args: - db (str): Database name. Possible values : - 'ocan', 'fmc', 'monitorenv_remote', 'monitorfish_local' - schema (str): Schema name - table_name (str): Table name - - Returns: - pd.DataFrame: Dataframe containing the entire table - """ - engine = create_engine(db=db) - return pd.read_sql_table(table_name, engine, schema=schema) diff --git a/datascience/src/utils/__init__.py b/datascience/src/utils/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/src/utils/database.py b/datascience/src/utils/database.py deleted file mode 100644 index 9f8031468e..0000000000 --- a/datascience/src/utils/database.py +++ /dev/null @@ -1,137 +0,0 @@ -import os -from typing import List, Union - -import pandas as pd -from sqlalchemy import inspect - -from src.db_config import create_engine, make_connection_string -from src.read_query import read_query - - -def get_tables_sizes(db: str, table_names: List[str]) -> pd.DataFrame: - """Returns a pandas DataFrame with the size of each table in tables_names, in Mb. - - db : 'ocan', 'fmc' - view_name : the name of the view to inspect. - """ - - query = f"""SELECT segment_name AS table_name, (bytes/1024/1024) AS size_Mb - FROM dba_segments - WHERE segment_name IN {tuple(table_names)} - """ - - table_sizes = read_query(db=db, query=query) - return table_sizes - - -def print_view_query_string(db: str, view_name: str): - """View the SQL query string that was used to create an existing view in an Oracle - database. - - db : 'ocan', 'fmc' - view_name : the name of the view to inspect. - """ - query = f"""SELECT TEXT - FROM SYS.ALL_VIEWS - WHERE view_name = '{view_name}'""" - - view_query = read_query(db=db, query=query) - - print(view_query.text.values[0]) - - -def find_table_schema(db: str, table_name: str): - """Looks for a table named table_name in all schemas of - the designated database and return the name of the schema - in which the table is found. - - If the table is not found, returns None. - - Possible values for db : 'ocan', 'monitorenv_remote', - 'fmc', 'monitorfish_local' - """ - e = create_engine(db) - insp = inspect(e) - - for schema in insp.get_schema_names(): - for table in insp.get_table_names(schema=schema): - if table == table_name: - return schema - - return None - - -def print_schemas_tables(db: str, schemas=None): - """Prints all schemas and associated tables in a database. - Optionnal argument 'schemas' takes a list of schemas to restrict the scan. - - Possible values for db : - 'ocan', 'monitorenv_remote', 'fmc', 'monitorfish_local' - """ - e = create_engine(db) - insp = inspect(e) - - for schema in insp.get_schema_names(): - if schemas is None or schema in schemas: - print(f"------------------------\n***{schema}***") - for table in insp.get_table_names(schema=schema): - print(table) - - -def pg_dump_table( - db: str, - table_name: str, - what: Union[None, str] = None, - inserts: bool = False, -) -> str: - """ - Runs ``pg_dump --schema-only`` on the selected database and returns the output as a - string. Useful to generate DDL statements of tables and to output test data as sql - scripts. - - * If ``db`` is ``monitorfish_local``, the ``pg_dump`` command will be run by the - machine on which the command is run, so postres must be installed. - * If ``db`` is ``monitorenv_remote``, the command in run through in the docker - container with ``docker exec monitorenv_database``. - - Args: - db (str): 'monitorenv_remote' or 'monitorfish_local' - table_name (str): the name of the table to export. - what (Union[None, str]): ``'data-only'`` ``'schema-only'`` or ``None``. If - ``None``, output both data and schema definition. Defaults to ``None``. - inserts (bool): if `True`, dumps data as INSERT statements instead of COPY. - Defaults to `False`. - - Returns: - str: output of ``pg_dump`` command - """ - try: - assert db in ("monitorenv_remote", "monitorfish_local") - except AssertionError: - e = f"'db' must be 'monitorfish_local' or 'monitorenv_remote' , got {db}" - raise ValueError(e) - - what_options = { - "data-only": "--data-only", - "schema-only": "--schema-only", - None: "", - } - - try: - assert what in what_options - except AssertionError: - e = f"'what' must be 'data-only', 'schema-only' or None, got {what}" - raise ValueError(e) - - connection_string = make_connection_string(db) - - cmd = f"pg_dump --dbname={connection_string} {what_options[what]} --table {table_name}" - - if inserts: - cmd = cmd + " --column-inserts" - - if db == "monitorenv_remote": - cmd = "docker exec monitorfish_database " + cmd - - stream = os.popen(cmd) - return stream.read() diff --git a/datascience/src/utils/graphs.py b/datascience/src/utils/graphs.py deleted file mode 100644 index 6fe183c115..0000000000 --- a/datascience/src/utils/graphs.py +++ /dev/null @@ -1,116 +0,0 @@ -import matplotlib.pyplot as plt -import pandas as pd -import plotly.express as px -from matplotlib_venn import venn2, venn3 - - -def dataframe_inventory(df: pd.DataFrame, title=None, fig_height=None): - fig = px.bar( - pd.DataFrame( - { - "Valeurs renseignées": df.count(), - "Valeurs distinctes": df.nunique(), - } - ), - orientation="h", - barmode="group", - labels={"index": "colonne", "value": "Nombre d'entrées"}, - title=title, - ) - if fig_height: - fig.update_layout(height=fig_height) - return fig - - -def compare_2_columns_values( - df: pd.DataFrame, col_name_1: str, col_name_2: str -): - plot_df = ( - df[[col_name_1, col_name_2]] - .dropna() - .assign(is_equal=(lambda x: x[col_name_1] == x[col_name_2])) - .groupby("is_equal")[[col_name_1]] - .count() - .T - ) - - plot_df = plot_df.rename( - columns={True: "Valeurs identiques", False: "Valeurs différentes"} - ) - - fig = px.bar( - plot_df, - orientation="h", - labels={ - "index": "", - "value": ( - f"Nombre d'entrées pour lesquelles {col_name_1} " - + f"et {col_name_2} sont renseignées" - ), - "is_equal": "", - }, - color_discrete_sequence=["blue", "green"], - ) - - fig.update_layout( - height=200, - width=900, - title=f"Cohérence des données {col_name_1} et {col_name_2}", - ) - return fig - - -def compare_2_columns_availability( - df: pd.DataFrame, - col_name_1: str, - col_name_2: str, - show_all: bool = True, - ax=None, - title: str = None, -): - - if ax is None: - fig, ax = plt.subplots(figsize=(12, 7)) - - if show_all: - all_ = set(df.index) - - set_1 = set(df[~df[col_name_1].isna()].index) - set_2 = set(df[~df[col_name_2].isna()].index) - - if show_all: - fig = venn3( - [all_, set_1, set_2], - set_labels=["Ensemble", col_name_1, col_name_2], - ax=ax, - ) - else: - fig = venn2([set_1, set_2], set_labels=[col_name_1, col_name_2], ax=ax) - - if title is None: - title = f"Complétude des données {col_name_1} et {col_name_2}" - - ax.set_title( - title, - weight="bold", - fontsize=16, - ) - - return fig - - -class pcolor: - """Helper class for formatted printing. - - Example : print(pcolor.BOLD + 'Hello World !' + pcolor.END)""" - - PURPLE = "\033[95m" - CYAN = "\033[96m" - DARKCYAN = "\033[36m" - BLUE = "\033[94m" - GREEN = "\033[92m" - YELLOW = "\033[93m" - RED = "\033[91m" - BOLD = "\033[1m" - UNDERLINE = "\033[4m" - END = "\033[0m" diff --git a/datascience/tests/__init__.py b/datascience/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/tests/conftest.py b/datascience/tests/conftest.py deleted file mode 100644 index 3d91004734..0000000000 --- a/datascience/tests/conftest.py +++ /dev/null @@ -1,225 +0,0 @@ -import itertools -import os -import re -from dataclasses import dataclass, field -from pathlib import Path -from time import sleep -from typing import List - -import docker -import pytest -from dotenv import dotenv_values -from pytest import MonkeyPatch -from sqlalchemy import text - -from config import ( - HOST_MIGRATIONS_FOLDER, - LOCAL_MIGRATIONS_FOLDER, - ROOT_DIRECTORY, - TEST_DATA_LOCATION, -) -from src.db_config import create_engine - -local_migrations_folders = [ - Path(LOCAL_MIGRATIONS_FOLDER) / "internal", - Path(LOCAL_MIGRATIONS_FOLDER) / "layers", -] - -cacem_migrations_folders = TEST_DATA_LOCATION / Path("cacem_database") - -host_migrations_folders = [ - Path(HOST_MIGRATIONS_FOLDER) / "internal", - Path(HOST_MIGRATIONS_FOLDER) / "layers", -] - -# Bind mounts of migrations scripts inside test database container -migrations_mounts_root = "/opt/migrations" - -migrations_folders_mounts = [ - ( - f"{str(host_migrations_folder)}:" - f"{migrations_mounts_root}/{host_migrations_folder.name}" - ) - for host_migrations_folder in host_migrations_folders -] - -test_data_scripts_folder = TEST_DATA_LOCATION / Path("remote_database") - -################################## Handle migrations ################################## - - -@dataclass -class Migration: - path: Path - major: int - minor: int - patch: int - script: str = field(init=False) - - def __post_init__(self): - self.script = read_sql_file(self.path) - - -def read_sql_file(script_path: Path) -> str: - cmd_lines = [] - with open(script_path, "r") as f: - for line in f: - if not line.startswith("--"): - cmd_lines.append(line) - - return "".join(cmd_lines) - - -def sort_migrations(migrations: List[Migration]) -> List[Migration]: - return sorted(migrations, key=lambda m: (m.major, m.minor, m.patch)) - - -def get_migrations_in_folder(folder: Path) -> List[Migration]: - files = os.listdir(folder) - migration_regex = re.compile( - r"V(?P\d+)\.(?P\d+)(\.(?P\d+))?__(?P.*)\.sql" - ) - migrations = [] - - for file in files: - match = migration_regex.match(file) - if match: - major = int(match.group("major")) - minor = int(match.group("minor")) - patch = int(match.group("patch") or "0") - path = (folder / Path(file)).resolve() - migrations.append( - Migration(path=path, major=major, minor=minor, patch=patch) - ) - - return sort_migrations(migrations) - - -def get_migrations_in_folders( - migrations_folders: List[Path], -) -> List[Migration]: - migrations = itertools.chain( - *[get_migrations_in_folder(f) for f in migrations_folders] - ) - migrations = sort_migrations(migrations) - return migrations - - -################################# Start test database ################################# -@pytest.fixture(scope="session") -def monkeysession(request): - mpatch = MonkeyPatch() - yield mpatch - mpatch.undo() - - -@pytest.fixture(scope="session", autouse=True) -def set_environment_variables(monkeysession): - for k, v in dotenv_values(ROOT_DIRECTORY / ".env.test").items(): - monkeysession.setenv(k, v) - - -@pytest.fixture(scope="session") -def create_docker_client(set_environment_variables): - client = docker.from_env() - yield client - - -@pytest.fixture(scope="session") -def start_remote_database_container( - set_environment_variables, create_docker_client -): - client = create_docker_client - print("Starting database container") - remote_database_container = client.containers.run( - "ghcr.io/mtes-mct/monitorenv/monitorenv-database:pg17-postgis3.5.1", - environment={ - "POSTGRES_PASSWORD": os.environ["MONITORENV_REMOTE_DB_PWD"], - "POSTGRES_USER": os.environ["MONITORENV_REMOTE_DB_USER"], - "POSTGRES_DB": os.environ["MONITORENV_REMOTE_DB_NAME"], - }, - ports={"5432/tcp": os.environ["MONITORENV_REMOTE_DB_PORT"]}, - detach=True, - volumes=migrations_folders_mounts, - ) - - timeout = 30 - stop_time = 5 - elapsed_time = 0 - healthcheck_exit_code = None - - while healthcheck_exit_code != 0 and elapsed_time < timeout: - print( - f"Waiting for database container to start ({elapsed_time}/{timeout})" - ) - sleep(stop_time) - healthcheck_exit_code = remote_database_container.exec_run( - f"pg_isready -U {os.environ['MONITORENV_REMOTE_DB_USER']} -d {os.environ['MONITORENV_REMOTE_DB_NAME']}" - ).exit_code - remote_database_container.reload() - print(f"Container status: {remote_database_container.status}") - print(f"Healthcheck exit code: {healthcheck_exit_code}") - elapsed_time += stop_time - continue - - yield remote_database_container - print("Stopping database container") - remote_database_container.stop() - remote_database_container.remove(v=True) - - -@pytest.fixture(scope="session") -def create_tables(set_environment_variables, start_remote_database_container): - container = start_remote_database_container - migrations_monitorenv = get_migrations_in_folders(local_migrations_folders) - - print("Creating tables for monitorenv database") - for m in migrations_monitorenv: - - print(f"{m.major}.{m.minor}.{m.patch}: {m.path.name}") - - # Script filepath inside database container - script_filepath = ( - f"{migrations_mounts_root}/{m.path.parent.name}/{m.path.name}" - ) - - # Use psql inside database container to run migration scripts. - # Using sqlalchemy / psycopg2 to run migration scripts from python is not - # possible due to the use of `COPY FROM STDIN` in some migrations. - result = container.exec_run( - ( - "psql " - f"-v ON_ERROR_STOP=1 " - f"-U {os.environ['MONITORENV_REMOTE_DB_USER']} " - f"-d {os.environ['MONITORENV_REMOTE_DB_NAME']} " - f"-f {script_filepath}" - ) - ) - if result.exit_code != 0: - raise Exception( - f"Error running migration {m.path.name}. Error message is: {result.output}" - ) - - -@pytest.fixture() -def create_cacem_tables(create_tables): - e = create_engine("cacem_local") - cacem_data_scripts = get_migrations_in_folder(cacem_migrations_folders) - print("Creating tables for cacem database") - - with e.begin() as connection: - for s in cacem_data_scripts: - print(f"{s.major}.{s.minor}.{s.patch}: {s.path.name}") - connection.execute(text(s.script)) - - -@pytest.fixture() -def reset_test_data(create_tables): - e = create_engine("monitorenv_remote") - test_data_scripts = get_migrations_in_folder(test_data_scripts_folder) - print("Inserting test data") - - with e.begin() as connection: - for s in test_data_scripts: - print(f"{s.major}.{s.minor}.{s.patch}: {s.path.name}") - connection.execute(text(s.script)) diff --git a/datascience/tests/mocks.py b/datascience/tests/mocks.py deleted file mode 100644 index 3307192bd5..0000000000 --- a/datascience/tests/mocks.py +++ /dev/null @@ -1,101 +0,0 @@ -from datetime import datetime -from io import BytesIO -from pathlib import Path -from typing import List, Union -from unittest.mock import MagicMock, patch - -import pandas as pd -from prefect import task -import requests - -from src.pipeline.generic_tasks import extract -from src.pipeline.shared_tasks.datagouv import update_resource - -from config import ( - TEST_DATA_LOCATION, -) - -def mock_extract_side_effect( - db_name: str, - query_filepath: Union[Path, str], - dtypes: Union[None, dict] = None, - parse_dates: Union[list, dict, None] = None, - params: Union[dict, None] = None, - backend: str = "pandas", - geom_col: str = "geom", - crs: Union[int, None] = None, -): - @patch("src.read_query.pd") - @patch("src.read_query.create_engine") - def mock_extract_side_effect_( - db_name, - query_filepath, - dtypes, - parse_dates, - params, - mock_create_engine, - mock_pd, - ): - def read_sql_mock(query, engine, **kwargs): - return query - - mock_pd.read_sql.side_effect = read_sql_mock - - return extract( - db_name=db_name, - query_filepath=query_filepath, - dtypes=None, - parse_dates=parse_dates, - params=params, - ) - - return mock_extract_side_effect_( - db_name, query_filepath, dtypes, parse_dates, params - ) - - -def mock_datetime_utcnow(utcnow: datetime): - mock_datetime = MagicMock() - mock_datetime.utcnow = MagicMock(return_value=utcnow) - return mock_datetime - - -@task(checkpoint=False) -def mock_check_flow_not_running(): - return True - - -@task(checkpoint=False) -def mock_update_resource( - dataset_id: str, - resource_id: str, - resource_title: str, - resource: BytesIO, - mock_update: bool, -) -> pd.DataFrame: - def return_200(url, **kwargs): - r = requests.Response() - r.status_code = 200 - r.url = url - return r - - with patch("src.pipeline.shared_tasks.datagouv.requests.post", return_200): - return update_resource.run( - dataset_id=dataset_id, - resource_id=resource_id, - resource_title=resource_title, - resource=resource, - mock_update=mock_update, - ) - -@task(checkpoint=False) -def mock_get_xml_files(): - return [Path(TEST_DATA_LOCATION / "vessel_xml" / "vessel_repository.xml")] - -@task(checkpoint=False) -def mock_get_xsd_file(): - return Path(TEST_DATA_LOCATION / "vessel_xml" / "vessel_repository.xsd") - -@task(checkpoint=False) -def mock_delete_files(xml_files: List[Path]) -> None: - pass \ No newline at end of file diff --git a/datascience/tests/test_data/cacem_database/V666.500__amp.sql b/datascience/tests/test_data/cacem_database/V666.500__amp.sql deleted file mode 100644 index 6fef7ab6dc..0000000000 --- a/datascience/tests/test_data/cacem_database/V666.500__amp.sql +++ /dev/null @@ -1,42 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS prod; -DROP TABLE IF EXISTS prod."Aires marines protégées"; -CREATE TABLE IF NOT EXISTS prod."Aires marines protégées" ( - id serial NOT NULL PRIMARY KEY, - geom geometry(MultiPolygon,4326), - mpa_id integer, - mpa_pid integer, - gid integer, mpa_name character varying, - mpa_oriname character varying, - des_id integer, - des_desigfr character varying, - des_desigtype character varying, - mpa_status character varying, - mpa_datebegin character varying, - mpa_statusyr integer, - mpa_wdpaid integer, - mpa_wdpapid character varying, - mpa_mnhnid character varying, - mpa_marine integer, - mpa_calcarea double precision, - mpa_calcmarea double precision, - mpa_reparea double precision, - mpa_repmarea double precision, - mpa_url character varying, - mpa_updatewhen character varying, - iucn_idiucn character varying, - subloc_code character varying, - subloc_name character varying, - country_piso3 character varying, - country_iso3 character varying, - country_iso3namefr character varying, - mpa_type text, - ref_reg text, - url_legicem text -); - -INSERT INTO prod."Aires marines protégées" VALUES (1, '0106000020E61000000100000001030000000B0000005D00000033241B253B8F4740505C514DF1D127C02E5EA3A1248F4740AB8DD1F8D4D127C0E900D932F78E4740070CA24081D127C0D68B2EEE888B4740204E9DD9D8CB27C0142D25CE768B474031352BE0B5CB27C08B7F6DA8648B47409C13B6DB92CB27C0F070F7851E8A47409D6661E40CC927C0474E2C4C728947400BF2B7DAAEC727C0A91548BD778947409DA5679790C727C0B834FA5CC6884740FDE6B69C18C627C0C34A85E6FC8647402EB3F17ACDC227C04CEB5CC48F7F4740BD80AF2D0AB027C012573D7C0C7947407D1EBBF0019A27C069D7457C9C7347409B2E1A54408127C0FF7446F88D7047406891E149D36D27C03723520F0270474026AFE8768C6A27C0B62C18ABEB6F47403F54386ACB6927C0BBD12354626F4740054C7D21626627C051AE8A2B596F47409482CE561B6627C000F7D7C9746D47400D7AFB96895427C071F3E6060A6D47406A6B482EF05027C078536466FC6C47405E7AAC222B5027C0BA2629ACDF6C474013845636204F27C0F1C21D74D66C4740436689E3B54E27C086BE9DE10F6C47404573E963CD4227C005709AEB366B47407D23D7C0873627C0BDEFD855926A4740A47011D6DE1B27C0974B9D36926A4740EEC605C6971B27C00F9D3D884D6B47407EBEBD3B71FD26C0FFD0E6EA8B6B4740049CB66858FA26C0E767AA1CA86B4740D4B32F6365F626C03F311918376D474026A197F926E526C04D93EBE8946D474033DDE13A80E026C0D1008242BC6D4740D33CCD5666DF26C0B6F5692C976E4740956D8E3DF2D526C046EFE713A06E474018B7EBE7AAD526C0E3F496DAFD7147403838D9238BBF26C0E68CBFDD4F764740FDD9C9865EAB26C01AAE318A97764740A0550E1540AA26C0F0844D4FBD7D4740208BBECAE89226C09E652B29F37D47401EC53302599226C010258AED8883474067B26BFA5F8526C0E87A509D06844740C240DC7F638426C08B231D7A2A84474091CD0E751B8426C01B60E7CEB5844740FE1F94AD178326C0A8CEB1FF77874740F8678372C77926C0E3C1324B508E4740290C870A516926C01CD66815AA8E47405D8DD7859C6826C0F8D94C025D97474073764912955A26C03AC37A4FD0A04740BCCB12E7DA5126C09466A40080A147402AFE0CEAA25126C020C0966AE6A14740FA021EE2485126C0FD2928B95BAB4740F05D6EDE654E26C008EC23B96DAB4740A0638247654E26C073814BE71AB447409E862016375026C0ECEACA4DB4BC4740BF93FB75325626C0ACC0E23308C54740DC128DBA346026C0A71E0A7DE6CC4740B691EDDE036E26C0C10709BB21D44740433CE3D44F7F26C012A3233090DA4740BEAE5753B49326C0CF03FABB0CE047409E2EE819BBAA26C05E993BAE77E447402F7EA39CDEC326C0288DD97AB7E74740C33EAF088DDE26C0B69FFD4BB9E947404E6D89902BFA26C02FBFB16E71EA4740BF81F4EC191627C0D08EA3B071EA4740B3B85385841627C02F3B8747C8E947409F6900ACC23327C0CA58701271E94740D2AF2C1E4C3827C0DE75EA7B6BE9474034CCED503F3927C0F908E43E3DE7474059060613CD5527C086AF46D315E747402C641A1EEF5627C025BB3A28FAE64740A7BC52AB535827C0EFF9D2293FE34740E315C6364B7327C0F62CE65D22DE47407368E0888A8B27C0E4B863A47CDA4740E5F788B9839727C0C316F1FE4ED847408C1A52F6D7A127C00320568FF3D1474009B0AED4B4B627C021B680D7AACF47402DC9A92EE8BB27C089DAE1286EC84740BCD39B3ED4CD27C0A757623B5CC84740864CD95DF8CD27C0A6B4A1484AC847407054A4871CCE27C0B18F8E4C38C8474018E51DC440CE27C01A042B60CAC04740C61634E4A4DA27C046D06E6EAFC04740B4482D4DC9DA27C0044D94D732BC4740CFC848B117E027C004D27BE10EBC4740BA1ED0633CE027C03F56CB9521B44740FFC8FD813FE627C0367BDAB4EAAB47401BFC715971E827C04581C28990AB4740B834843774E827C0BE3CB3944EA3474022D63015D1E627C0CFE8D4C51A9B4740570FF4BD6AE127C0B6FB383B20934740EE239A825DD827C033241B253B8F4740505C514DF1D127C0090000002F896C4D5EA44740D99AEAB2602A27C0562A7D285EA447401CEB34A2192A27C04D890A2655A447407BAA0DEB192A27C0BFEC931355A447401D87AE62F62927C038F6B20E43A447401B6F57F4F62927C04F2FA03343A44740DBB515053E2A27C097AA10364CA447405EF63CBC3D2A27C0DF6C89484CA44740DD199C44612A27C02F896C4D5EA44740D99AEAB2602A27C00700000096F8BA4469AA4740BAF5421D302527C06F85390C69AA47401A790D85C52427C089D7F10960AA47407CFF4ECFC52427C01E0B9C2F60AA4740FBA772DF0C2527C07737522D57AA47407C2EB4290D2527C04051274057AA47409B99CEB1302527C096F8BA4469AA4740BAF5421D302527C0110000004F901A0B71A44740FA8AC574C32B27C010F02EC170A447403A945153352B27C0DFB6A3C379A44740DBD4780A352B27C0D1CE28B179A447405BB11982112B27C00908B4AE70A44740DA07FBCA112B27C0A75DC08970A447401D5845BACA2A27C0DF964B8767A447409BAE2603CB2A27C08FD4D27467A447403A8BC77AA72A27C0C70D5E725EA44740BBE1A8C3A72A27C0D7F5D8845EA447401C05084CCB2A27C00D2F648255A447409AC4E094CB2A27C0A6B355A755A44740587496A5122B27C037A0CCA95EA44740FAB4BD5C122B27C020E736E15EA447401BF1C9F57C2B27C0A7D3ADE367A447409C9AE8AC7C2B27C0007EA10868A447407B4A9EBDC32B27C04F901A0B71A44740FA8AC574C32B27C001010000C6E488406BA647405B9A59B3E42E27C0A0A0EB2D6BA64740FA3B142BC12E27C017F2DE378FA647407BF25705C02E27C0F6AD41258FA647401B2B1B7D9C2E27C02056B32CAAA647405B7AB8A09B2E27C037EC131AAAA64740FDB27B18782E27C0505C0A1FBCA647403A608C85772E27C0E9A6660CBCA64740DA984FFD532E27C0FF165D11CEA64740FB174F6A532E27C09161B9FECDA64740B85012E22F2E27C02886AB03E0A64740BBCF114F2F2E27C0C1D007F1DFA647407B08D5C60B2E27C047BD7EF3E8A647407CE5477D0B2E27C0E107DBE0E8A647403AB513F5E72D27C0281A54E3F1A647403D298FABE72D27C0FE3EAED0F1A64740DC615223C42D27C00F189CD503A74740F91B3890C32D27C0E93CF6C203A747409C54FB07A02D27C0314F6FC50CA74740B9C876BE9F2D27C03F4EC7B20CA7474059013A367C2D27C00F153CB515A7474059DEACEC7B2D27C0EF38EE8F15A74740B9E63BDC342D27C0802565921EA74740B9C3AE92342D27C08F24BD7F1EA7474079FC710A112D27C099C52F8227A747407842DCC0102D27C0A6C4876F27A747401B12A838ED2C27C06E8BFC7130A747403DEF1AEFEC2C27C07F8A545F30A74740DB27DE66C92C27C0882BC76139A74740DB6D481DC92C27C0D6041D4F39A747409C3D1495A52C27C020808D5142A747409A837E4BA52C27C0307FE53E42A747403CBC41C3812C27C078FA55414BA747403C6BA379812C27C0D7D2031C4BA74740990A3B693A2C27C01E4E741E54A747409B50A51F3A2C27C06F27CA0B54A747405A207197162C27C0F77C380E5DA747403B38CA4D162C27C047568EFB5CA74740F90796C5F22B27C0C9ABFCFD65A74740F94D007CF22B27C0168552EB65A747409A1DCCF3CE2B27C0A0DAC0ED6EA747409A3525AACE2B27C02E8E14DB6EA747405A05F121AB2B27C0AFE382DD77A747403CB452D8AA2B27C08D702CB877A74740BC53EAC7632B27C050A098BA80A747409A024C7E632B27C0202EEAA780A747405CD217F63F2B27C0E05D56AA89A747405A5368AC3F2B27C06E11AA9789A74740FB2234241C2B27C0701B149A92A74740FCD195DA1B2B27C09082BB7492A7474059712DCAD42A27C0CF6623779BA7474059898680D42A27C030A8C8519BA74740BB281E708D2A27C02FB23254A4A74740BC4077268D2A27C0C7CDD52EA4A747401DE00E16462A27C0488C3B31ADA74740FAF767CC452A27C0D09CD7D3ACA747409D9D6B23942927C050DE71D1A3A747409B1C1B6D942927C0A0B7C7BEA3A747405CECE6E4702927C01FF961BC9AA747405AD48D2E712927C03E6009979AA74740BA73251E2A2927C045EDA79491A74740BCC4C3672A2927C0D1A0FB8191A747407B948FDF062927C0D82D9A7F88A747407BE52D29072927C02807F06C88A747403DB5F9A0E32827C031948E6A7FA747403B9DA0EAE32827C0CF463A457FA747409A3C38DA9C2827C08FF9DA4276A747409AF6CD239D2827C0E7D2303076A747405DC6999B792827C0E85FCF2D6DA747405D802FE5792827C0F85E271B6DA74740FB4FFB5C562827C0BF11C81864A74740FA0991A6562827C0CF10200664A74740B9D95C1E332827C097C3C0035BA74740BA93F267332827C0A6C218F15AA7474058CCB5DF0F2827C02F9BBBEE51A747405AEF4229102827C006C015DC51A747401BBF0EA1EC2727C08F2557D73FA74740FA6D2034ED2727C0674AB1C43FA74740BA3DECABC92727C0ED2254C236A74740BC6079F5C92727C07F6DB0AF36A7474059993C6DA62727C0472051AD2DA747405A25C1B6A62727C0E06AAD9A2DA74740FAF48C2E832727C06743509824A74740FAE90878832727C0B7B3AE8524A747409CB9D4EF5F2727C03D8C51831BA747409CAE5039602727C099FCAF701BA7474039E713B13C2727C01FD5526E12A747403E7398FA3C2727C0B71FAF5B12A74740D9AB5B72192727C0BF43565909A74740D9A0D7BB192727C0588EB24609A7474079D99A33F62627C09F8C574400A747407BCE167DF62627C0B822B83100A747401807DAF4D22627C001215D2FF7A64740FC644D3ED32627C05891BB1CF7A64740BA9D10B6AF2627C068D90918E5A647407B59F748B02627C07E6F6A05E5A6474019FBB1C08C2627C0C66D0F03DCA647401B59250A8D2627C0A82972F0DBA64740BA91E881692627C0EE2717EED2A64740965853CB692627C087097CDBD2A647403CFA0D43462627C0D62BC8D6C0A64740FBF0DAD5462627C0760D2DC4C0A64740BCC0A64D232627C08031D4C1B7A647409CF00897232627C057ED36AFB7A647403C29CC0E002627C06511DEACAEA647401AC22558002627C0813E479AAEA64740BCFAE8CFDC2527C08E62EE97A5A647409B934219DD2527C026445385A5A647403935FD90B92527C0F88DFC829CA647401B655FDAB92527C0519563709CA64740BA9D2252962527C01FDF0C6E93A64740999F739B962527C078E6735B93A647403AD83613732527C046301D598AA647401C71905C732527C0605D86468AA64740BC124BD44F2527C030A72F4481A647409C149C1D502527C050D4983181A647403BB656952C2527C0161E422F78A647401C4FB0DE2C2527C0F670AD1C78A64740BAF06A56092527C0FE94541A6FA647407C5BB39F092527C0C03A2BF56EA64740DC35318FC22427C04EAAD6F265A647409BA079D8C22427C02DFD41E065A647403B4234509F2427C0B76CEDDD5CA647401AAD7C999F2427C03738C6B85CA647405CF0F188582427C0C8A771B653A647401C5B3AD2582427C007994C9153A647407B9EAFC1112427C057A9B28526A647403C93DD2F132427C0B747227326A64740DD3498A7EF2327C0672D2D6C0BA647401A543583F02327C0C7CB9C590BA64740BBF5EFFACC2327C0D7864C5702A647407A9B1E44CD2327C0004BBE4402A647401CA6D0BBA92327C050E06B42F9A54740DB4BFF04AA2327C02FCADF2FF9A547407B56B17C862327C07F5F8D2DF0A547403BFCDFC5862327C0286F031BF0A54740DA06923D632327C07704B118E7A547407915B886632327C020142706E7A547401C206AFE3F2327C04F4536FFCBA547403BB5D3D9402327C0A735C011CCA547409CAA2162642327C0F7CA6D0FC3A547403B223FAB642327C00FE1F921C3A547409A178D33882327C01E9CA91FBAA547403CF8A17C882327C0F9402F32BAA547409DEDEF04AC2327C0872B93268DA54740D9E76072AD2327C020F61A398DA547403BDDAEFAD02327C0F6FA73347BA547409907D08CD12327C0CD9FF9467BA54740FCFC1D15F52327C0AFA4524269A547401BF92DA7F52327C0C023D65469A547409C85842F192427C08F6D7F5260A547401A388878192427C05E12056560A547407A2DD6003D2427C08615035E45A547405B45E1DB3D2427C09D94867045A54740B8D13764612427C07702D76B33A54740FA9F36F6612427C0C75B587E33A547405B95847E852427C060EFAA7921A547407C357210862427C0EE222A8C21A54740DB58D198A92427C0C6907A870FA54740FC61B62AAA2427C058C4F9990FA547405AEE0CB3CD2427C067E8A09706A54740D972FFFBCD2427C037F61DAA06A547405AFF5584F12427C08F3C11A3EBA44740FDF5245FF22427C0277090B5EBA4474058827BE7152527C06F6E35B3E2A44740DB6F6530162527C0417CB2C5E2A447403BFCBBB8392527C00E53FAC0D0A447405940874A3A2527C0E06077D3D0A44740BB63E6D25D2527C0285F1CD1C7A447403A51D01B5E2527C0394797E3C7A447409C461EA4812527C048F8DCDEB5A44740BB8AE935822527C057E057F1B5A447401A1740BEA52527C0E0B8FAEEACA447409C6D2107A62527C0287B7301ADA44740FAF9778FC92527C0AF5316FFA3A447407D5059D8C92527C0FE158F11A4A44740D8DCAF60ED2527C0C7C82F0F9BA447405A9C88A9ED2527C00F8BA8219BA44740D8BFE731112627C0D73D491F92A447405A16C97A112627C02900C23192A44740B90B1703352627C0278D602F89A447403BCBEF4B352627C0774FD94189A447409BEE4ED4582627C080DC773F80A447401CAE271D592627C00779EE5180A44740793A7EA57C2627C0CF2B8F4F77A44740F9F956EE7C2627C097A2036277A44740591DB676A02627C06055A45F6EA44740DA4586BFA02627C0B7688F846EA44740BA8C44D0E72627C0B7F52D8265A4474018B51419E82627C0679C5B2866A44740DA2E53E4272827C0A680C32A6FA447405C06839B272827C000AB0EAC6FA447403BA2FA55202927C0C5DA7AAE78A44740BEE2210D202927C06D47E7E578A44740FBB536A68A2927C0F09C55E881A44740795F555D8A2927C0106D4B0D82A447403B0F0B6ED12927C097C2B90F8BA44740BDB82925D12927C0AE92AF348BA447407968DF35182A27C0F70D203794A44740FB11FEEC172A27C007F69A4994A4474059074C753B2A27C04F710B4C9DA44740DC47732C3B2A27C0DFA48A5E9DA447403E3DC1B45E2A27C05FFAF860A6A44740B84FD76B5E2A27C0F72D7873A6A447401A7336F4812A27C038A9E875AFA447409CEE43AB812A27C0CFDC6788AFA44740FA11A333A52A27C01058D88AB8A4474079F6A7EAA42A27C0A88B579DB8A44740DC190773C82A27C0A92CCA9FC1A447405B95142AC82A27C03E6049B2C1A44740BC216BB2EB2A27C04701BCB4CAA447401B067069EB2A27C0965A3DC7CAA447407A29CFF10E2B27C09FFBAFC9D3A44740FA0DD4A80E2B27C0EF5431DCD3A44740599A2A31322B27C0F7F5A3DEDCA44740BD7E2FE8312B27C0514F25F1DCA44740190B8670552B27C017169AF3E5A447409B588227552B27C0696F1B06E6A44740FBE4D8AF782B27C0EF5B9208EFA4474059C9DD66782B27C05734972DEFA4474019E28A77BF2B27C027FB0B30F8A447407D987E2EBF2B27C0519C9F8CF8A447407CBF26D8702C27C097AE188F01A54740DC751A8F702C27C088D221B401A547409C8EC79FB72C27C066098DBB1CA54740BAB1A2C4B62C27C0FFD314CE1CA547401AA7F04CDA2C27C048E68DD025A5474078C6DB03DA2C27C0DFB015E325A54740D952328CFD2C27C030C38EE52EA547403D721D43FD2C27C088B318F82EA547409A676BCB202D27C0E0FD0CFD40A547403C0F3939202D27C038EE960F41A547409C9B8FC1432D27C0A07006175CA547409B9D2EE6422D27C0F86090295CA54740FC927C6E662D27C0D7F6882E6EA547409B7530DC652D27C030E712416EA54740FB018764892D27C0077D0B4680A54740974D32D2882D27C02893975880A54740FB42805AAC2D27C00129905D92A547407CF722C8AB2D27C0E0641E7092A54740D9EC7050CF2D27C0B8FA1675A4A547405B7302BECE2D27C0585CA787A4A54740BB685046F22D27C037F29F8CB6A5474039EFE1B3F12D27C0D053309FB6A547409C4D273C152E27C06F0F2BA4C8A547401A3DB0A9142E27C0CF96BDB6C8A547407B32FE31382E27C06F52B8BBDAA54740DCF3759F372E27C0C7D94ACEDAA547403AE9C3275B2E27C09737C8D0E3A54740787E7BDE5A2E27C0B7E45CE3E3A54740DBDCC0667E2E27C08642DAE5ECA54740F9DA6F1D7E2E27C0A6EF6EF8ECA547405BD0BDA5A12E27C0C5F66DFDFEA54740DD632413A12E27C0E6A30210FFA547401DC2699BC42E27C00EAB011511A647407DBEC708C42E27C0F07D982711A64740DA1C0D91E72E27C0C6E488406BA647405B9A59B3E42E27C00500000067EE02D43DB04740FB1B8BD8ED0E27C0601CBEAD3DB04740FEE3F0C8A60E27C067F10BAA2BB047407B6ADE5FA70E27C0F0774CD02BB047409D0B706FEE0E27C067EE02D43DB04740FB1B8BD8ED0E27C005000000091BCF4EE3AF47407B3BDF9B1B0E27C04623B33BE3AF474078880914F80D27C0A720DB39DAAF4740BD86665FF80D27C0A7F2F44CDAAF4740BCA233E71B0E27C0091BCF4EE3AF47407B3BDF9B1B0E27C0090000003EC1EC0861B047409C9FF9D4650D27C03658CAF560B047409B1A354D420D27C0500F9EF769B047403D57BE01420D27C04D3D59D169B047403AB62CF2FA0C27C0F8AB87CF60B047409A79A33DFB0C27C0AF96229660B0474097534DA6900C27C05805519457B04740DA7FBBF1900C27C0200A190758B04740DDF97820660D27C03EC1EC0861B047409C9FF9D4650D27C007000000DEA2D40A45B047405A701ED2980B27C09FE93BAB44B047403A7B252BE70A27C03ECFA7A732B04740B9A5F0C1E70A27C0F8C6C3BA32B04740B9C1BD490B0B27C0CF0C91BC3BB047407B954FFE0A0B27C0105D07093CB047409905841D990B27C0DEA2D40A45B047405A701ED2980B27C02D000000809C4F6E32B04740BA51892A7D0A27C0BFA4335B32B04740BACCC4A2590A27C00F9FFC5C3BB047405DA05657590A27C09081DE493BB047405B1B92CF350A27C0E07BA74B44B047401C862C84350A27C0E1406B2544B047401B4E9274EE0927C0711532274DB04740DC212429EE0927C06FDAF5004DB04740DA809219A70927C03F89BA0256B047409BEB2CCEA60927C0414E7EDC55B047407BB392BE5F0927C011FD42DE5EB047403B1E2D735F0927C0529C04B85EB0474039E69263180927C05E25C7B967B04740FC502D18180927C096C4889367B04740DC189308D10827C0A84D4B9570B0474098EC24BDD00827C0690A2B8270B047409A676035AD0827C0AF6DEB8379B047405B3BF2E9AC0827C02E50CD7079B047405A1F2562890827C008F14B748BB04740BC2F40CB880827C0C644094E8BB04740BB8EAEBB410827C0278CCE4870B047409CAA019E420827C0A76EB03570B047409B8E34161F0827C0AF327B220DB047403CD75553220827C0A60495350DB047403DF322DB450827C05EA1D43304B04740795A7726460827C0974DEC4604B047407B7644AE690827C0F5FDA841E9AF47401B1539906A0827C0AF30D667E9AF47401D4DD39FB10827C0770CCC60C5AF4740D9F6F9CCB20827C0306DDF73C5AF4740DBA9CF54D60827C0165B5A70B3AF4740391C56EBD60827C0CFBB6D83B3AF47403A382373FA0827C0FF0CA981AAAF47405A7166BEFA0827C0B76DBC94AAAF47405A8D33461E0927C0E7BEF792A1AF47409DC676911E0927C08F952DCCA1AF4740BB48EF28890927C0D0E748D3C5AF47401AFBEAFB870927C0178735C0C5AF4740FC471574640927C01FF10CC9F2AF47401A379AFB620927C01FC326DCF2AF47401AEA6F83860927C08046B2DF04B04740BB84BEEC850927C0EF70FB1805B04740BB6F2E84F00927C03F6BC41A0EB047409B08DA38F00927C037B32B670EB047409B780E587E0A27C0809C4F6E32B04740BA51892A7D0A27C00B000000EFA32468AFB047407A6F8F15640827C0B1600455AFB047407B81D38D400827C001B67E58C1B04740DDCCD4F63F0827C0782F131FC1B04740DBA67E5FD50727C0A6259D1BAFB047407D5B7DF6D50727C0E768BD2EAFB047407EE0417EF90727C02151012DA6B04740DCA3B8C9F90727C020BA2340A6B04740DCBF85511D0827C0967C653E9DB047401B83FC9C1D0827C0E028A8649DB047401B248EAC640827C0EFA32468AFB047407A6F8F15640827C0', 735, 733, 8342, '24 MILLES AUTOUR DE L''ARCHIPEL DES GLORIEUSES', '24 milles autour de l''archipel des Glorieuses', 47, 'Réserve naturelle nationale (zone de protection renforcée)', 'national', 'designated', '2021-06-08', 2021, 999999735, '999999733', NULL, 2, 7475.61062182309, NULL, NULL, NULL, NULL, '2021-06-11', 'Not reported', 'FR-TF', 'Terres australes françaises', 'FRA', 'ATF', 'Terres australes françaises', 'MPA Type 1', 'Résumé reg', 'url legicem 1'); -INSERT INTO prod."Aires marines protégées" VALUES (5, '0106000020E6100000010000000103000000010000008A0000004095E4CAEA762240108FF404AA5C45405F771262457A2240F7E693BC8A5C4540BF926B3D557E224037F7A4D4645C4540207C9949507E22406EA4ADF6625C454040A9979B4C7E2240006DB012615C45406000BB0F4A7E22402024C6C35F5C4540808ECDC3467E2240F0BD62875D5C45409F1F8EBA457E2240F0ECE83B5C5C4540DFDAECBC4B7E2240D85BA231555C454020796184537E224040DAD4804E5C4540C01794CC5E7E224040AFB496465C4540E054DE6D627E22401715D464445C4540E071C8A8607E2240586BC641405C454080B3D6DB5C7E224070A6634A3B5C4540E0BF76B9537E2240A06BFC15345C45402061EE794F7E2240485F419A315C4540BF1060FB437E2240B776FBDC2B5C4540DF509F47397E22402FC77E58275C4540201EC466317E22406758B482245C454060C3D4752C7E2240880732DA215C454040A271F7257E224041D1D7541F5C4540815CCDC01C7E2240297F81371C5C4540C0107A750F7E2240FF1744AE185C45403F048894FC7D224070BA0D86145C454020D16D4AF87D2240506A8ECA125C45402047EB29EB7D224038D6E3C30E5C4540A0ADA347DF7D2240D02DDA070C5C4540802C152BD57D224078D05A0E0A5C45405F59F2D6CB7D2240E0E48C68085C4540FF374734B97D2240E724FCA1055C45409FF50996A67D224068CB1987035C4540A06F98899E7D22404668899D025C4540006BC281867D2240AE6F0E74005C4540DFA387D97F7D2240DF79BDBCFF5B45406162E3FC787D224028436D9FFE5B4540C09E7BB1257D2240F0AC80BC035C45404020CE05267D224039DA94D2035C4540800CA2BD137D2240F13DBCE4085C45407F937E79037D2240106B64360C5C4540FFBBD4B4FD7C224008E330630D5C454060BAD491EE7C224027E73205105C454060043A77E17C22404FB29681115C4540A043B9A4D27C2240F09E3505135C45405F113188BC7C224088CBCFB2135C454020151EF29E7C2240F8798739135C45405FA0811E8B7C22402018C379115C45405F626A13737C224010EA28F50F5C45403F69D88A5E7C2240683B118A0E5C45409F67E8DC437C2240976B91E70C5C45407FBE5469147C22407937DF200B5C45407FD47E4FD27B22407F7F3E220A5C4540C039F635907B2240883E46E30A5C45404093A4CE507B22402800C7EA0B5C45401F3DED32F47A2240DFC73F030F5C4540FF9034DBBC7A224098251C75125C454040BA462EAC7A2240A73BAFAE135C4540C06800908F7A2240801C2B03175C454041F9FB927B7A22404052E15B1A5C4540C1CAEE8A5F7A22404F59E5F31E5C454000FCA515447A2240F8CD22E6225C4540C0AFE8DB247A2240A0BA2C05285C454080B4C6A0137A2240C02C66E4295C454020E92472F9792240605E9D1C2B5C4540C05D05AADF792240A81AD8B82B5C4540801FF373D679224090E38FBB2B5C4540BF7FEA27B5792240CFAF0D122B5C4540DF13111C9C79224088BE2C342A5C454020F7E3A584792240FF6D333B295C4540A05C66276F792240B0C9251E285C4540C03B10914E792240419B605B265C45409F6606D32B792240889AD9C8245C4540C017EBF017792240EF883A0E245C4540E0A333DAF1782240E00F46B9225C45400080EABFD17822400717D76A225C4540603AEEADAB782240E0C39665225C45405F37D6D29778224087A96F91225C454020128F75857822406F4987E7225C4540DF80172F7678224068218F4D235C45400068A333637822402F1E490F245C454080991F1A3E78224040ABF45C245C4540207293E1107822400F0FAF8B255C4540405DC9CDF1772240102054F3265C4540000D62ACDE772240E02B5F3C285C4540200E23CED477224007E427E2285C4540804E4CCDD4772240A816FFE1285C454060B3577D80772240A72F5463215C4540C0BDB58379772240E0ABBB51225C4540E0771C97557722409F8A437F265C4540608979E93577224076DB24BD2A5C4540C099ED77197722404F4C412B2E5C45409FAB4A410877224058955A48305C45403FC54284EA7622404F52A61F345C45403FF1F351D9762240702CB4B5365C4540BFDF93DACD76224090628E25395C45407FB2EECEC3762240D73706833C5C454080D306BDBD762240977DAE4E3F5C454060E69C8EB676224047ED275E435C4540C0F38B38BB762240782FCF01445C4540A0AEBB67C7762240C70C7B44465C45400073F089CB76224010877B4B4A5C4540E06BD076D0762240CF0201F34F5C4540DF3BA9B0CB7622405765D0EA535C45409F1C2BBCC1762240184A6454565C4540E0D8E068BA762240E1064A44575C4540006E6B52A476224057D5FEE0595C4540BF9EBC1694762240484CA2C15A5C45407F76CF0D8F762240E04834DC5B5C45407F54CE3A8A76224030E7025F5D5C45401F9730EE94762240588062D65E5C4540807D074E9C7622403FF6B75A605C4540E0B47115A276224099771921635C4540A00A052DA37622405026366B655C4540C00EAA81A476224070C3E534685C4540A0A8E7FBAE76224081AD893A705C454020AF8EF0B176224050614912745C454020E09F9EB3762240887DF245755C45401F3F9C13BD762240B086BECD785C4540404D2B5AC076224070E075007B5C4540608DC6F0C4762240BF98104D825C4540E0F62906C9762240619A14DF835C4540A06CC27ECE7622405032A708865C4540C08242B1D3762240C0A7D396875C454080902A5FD7762240A0A31059885C454060C350D0EB762240172F23B08A5C4540A03B9611F3762240EF4DC1848B5C4540C0010595F3762240DFE3CD938B5C45405F869D940F77224020A850668E5C4540E0E2EAF816772240B91597EA8F5C45404043BAC31977224077579819915C454000E770662177224018873E7B955C4540DF05153E23772240180D9757995C4540C0F69AEB227722406E29EDFB9A5C4540A04E6C4A217722405FD1443D9C5C45406074D7771D772240B0EFBC879D5C4540C098C853F6762240E8FEA19BA85C4540A09D0C21F676224077A007A4A85C4540C0995736F2762240FFBC014AA95C45404095E4CAEA762240108FF404AA5C4540', 92, 92, 3804, 'AGRIATE', 'Agriate', 3, 'Domaine public maritime (Conservatoire du littoral)', 'national', 'designated', '2006-04-14', 2006, 106767, '106767', 'FR1100014', 2, 0.447245166819938, 0.4286, 11.05711, 6.683702, NULL, '2019-09-10', NULL, 'FR-COR', 'Corse', 'FRA', 'FRA', 'France métropolitaine'); -INSERT INTO prod."Aires marines protégées" VALUES (7, '0106000020E6100000010000000103000000020000007300000052B096B302B962C0D8B5EC8C7E8C31C04E45184604B962C0A62EF00E6D8C31C0A40C64C103B962C04DC650896A8C31C0E6A3874503B962C0FC0C0656688C31C09A28B60603B962C0EE03C4D7678C31C002CBFDD802B962C0D2A94ECF688C31C0B4E49BAA02B962C0A86995096A8C31C04094717302B962C051B1C2DA698C31C0759DA83002B962C0159B52EB688C31C0A4F22DD201B962C0A4251240668C31C07CE7A9A701B962C089364C14648C31C0FA327D9401B962C0B0E3F9C55E8C31C02D84CD5F01B962C0CB2E0E5A588C31C06D5F8E2301B962C01C582BCF518C31C08B028BB200B962C0FA56E5F34B8C31C06EC4E75F00B962C0E533BC10488C31C09692465F00B962C03A601CFD478C31C01738B1ECFFB862C076D327C4438C31C0533B0F27FEB862C0AFD64239358C31C063AD102CFCB862C009922461298C31C0EC76D25FFCB862C0E063ED07278C31C0F7EC3377FCB862C024F533FF258C31C07A29E92FFCB862C0C32BEF20258C31C078CD4CBDFBB862C0FD84FB45238C31C075FF7044FBB862C06E51225E228C31C005689D69FAB862C0889AC5531F8C31C0996BD6CFF9B862C0515EA8B01B8C31C001DE09F1F8B862C053EB81FB168C31C078D4F2F8F7B862C0A7480C3A128C31C0770FE62AF7B862C0E02066DC0D8C31C0BA9EB429F6B862C0FB83E1DD098C31C092EC1F36F5B862C0C051CDDA058C31C004A8194CF4B862C0ACA89A69028C31C0A2C88863F3B862C0F00E9751FE8B31C031F9AAC7F2B862C0FEDD1FD9FA8B31C0FBA49E2CF2B862C0C6AF2339F68B31C02FFB7679F1B862C0BF2DB68CF18B31C0B38EC5D9F0B862C01883BF84ED8B31C0F3AFADF9EFB862C0FB19A02DE88B31C0F886B7D3EEB862C07D89D5DCE28B31C08E30AC21EEB862C06BB671D8DE8B31C020466465EDB862C01A9BD6E2DA8B31C07E300F77ECB862C0250FC21BD78B31C026A2EF5FEBB862C028C19BE1D38B31C06AE92198EAB862C084548063D18B31C0077FECA6E9B862C077F4ACC8CD8B31C09D2CA490E8B862C056DFB710CB8B31C03AE997DDE7B862C05CCF7F70C98B31C062D09E7FE7B862C03B41C465C78B31C031B2A751E6B862C0D637E379C38B31C0C5B9D879E5B862C08B23F28BC08B31C0969E22D3E4B862C039966286BF8B31C0439B473BE4B862C04A4AF602BF8B31C026E50DC7E3B862C07A157B14BD8B31C056115140E3B862C04F1ABB6DBA8B31C0D9C927B3E2B862C02B9A3E2DB98B31C098B9F93DE2B862C0D535AB55B98B31C033575D9CE1B862C0E89A6C0EB98B31C0A66033ECE0B862C01218A91ABA8B31C063D2341DE0B862C085579CACBA8B31C0C98A6B25DFB862C081D4EEFBBA8B31C00CEC4812DEB862C04461AE43BA8B31C08A085CD0DCB862C02B8AFDE9B78B31C0261E80F9DBB862C0834DD01EB68B31C0C43F1E99DBB862C057E92766B48B31C057179196DBB862C08CFBE167B28B31C0E030F54EDBB862C01EA96D13B08B31C04555DBB1DAB862C0150DF7ECAE8B31C02B8783A3D9B862C04D69601AAD8B31C0EF678CD7D8B862C0F7E9A5FAAA8B31C0F2DC7E6BD8B862C072A43D6CAA8B31C0995F332CD8B862C03615C797AC8B31C0DAAB31E9D7B862C0F52AD9A0AD8B31C0CB6AE2B5D7B862C084711941AD8B31C0CE5CC9B0D7B862C093DDBCB4AB8B31C0A0F790D1D7B862C061F86F7DA88B31C0996074DCD7B862C02DBA25E9A48B31C0F1B0969ED7B862C00F3C79B9A28B31C0D686150BD7B862C04953F633A08B31C0808C99A0D6B862C03DFC4B389F8B31C0902C5FFED5B862C00A16E6AC9D8B31C0DFF2B578D5B862C0629F8F6B9C8B31C0131DB80BD5B862C021FDE5579A8B31C0055B7A88D4B862C0CB750062988B31C09B5888C8D3B862C0AA70BCFF948B31C0C1D70709D3B862C0553B9EE7908B31C004D0DB70D2B862C0D7076EB78D8B31C0EB9D49EAD1B862C049076D308A8B31C0171236ACD0B862C06E1F8B64848B31C04F009148CFB862C06D39D7097C8B31C074F30137CDB862C0B3726697738B31C0396ADE44CBB862C0C4442A86658B31C026292CAF79B862C07BFFF326D28B31C0E2DAF0357EB862C07052E810F48B31C07A59C8E885B862C066E1AB6D2C8C31C0B49D4A948CB862C0E89F537D5E8C31C04CC0CB0093B862C04E9AB4CA918C31C0E243683E98B862C01EBBBDB7C08C31C02E482FC29DB862C0A41A632CF18C31C01EDA5051A2B862C0E1242B181E8D31C0B156FA8BA5B862C0F50698C2408D31C079262A90A9B862C040D618656D8D31C0DF98055AADB862C0CF36C003998D31C01A375379B1B862C04D1D89C9CD8D31C0C6E02972B4B862C0873FA9C3F78D31C04BEEAD12B8B862C0CD9091752D8E31C05B6E99ECBAB862C09D13F22E5F8E31C05752686CBCB862C0324D3D1B7B8E31C0CAE8FAA0BEB862C0C39BE2DCA58E31C0DF9370E5BFB862C0B4DBC9B0BE8E31C074C1057AC1B862C03D80C9BFD58E31C0F6246D80C2B862C010CC19EFE58E31C0492DFCD2C3B862C07B7B5CDCF18E31C090014E62D7B862C0680A77DF268E31C052B096B302B962C0D8B5EC8C7E8C31C09500000014DFEF05D9B862C09B1C41761A8D31C084A27C1CD9B862C0BC782B631D8D31C03677DA14D9B862C05E1134C8228D31C0A51C5FCED8B862C075BB65D6288D31C05388018BD8B862C025630D362F8D31C03B2CD63FD8B862C01F7AA964378D31C0FF9B770BD8B862C05FDC8FF13C8D31C0677B28FBD7B862C04E94D398458D31C09DDE9F3FD8B862C0947618064E8D31C08379B69CD8B862C02A7AD856568D31C0D46DFADBD8B862C06952EAD65F8D31C0B08784D0D8B862C049EBBD1F698D31C0C79147A7D8B862C06F1791E0728D31C08917B58DD8B862C0C81955F1798D31C0E969ACAFD8B862C0CE7F6FD27E8D31C09677E584D8B862C00E05CC65838D31C088329E66D8B862C0D59910B2878D31C07B43C74ED8B862C061C5C6A58C8D31C0EDEB84EBD7B862C07FDA95F08D8D31C015F4AA66D7B862C0F0D882F98F8D31C03C92F4EFD6B862C0CDF687AA928D31C038A88952D6B862C0C7FC71C2978D31C05A3C826CD5B862C07FB966B0998D31C067762DCBD4B862C06A2EAACE9A8D31C0392AD9CFD4B862C0E9086EB19C8D31C0984F2404D5B862C06FF997DD9D8D31C035998588D5B862C0621182819E8D31C01CC469CFD5B862C00173D82AA08D31C0E107DCE6D5B862C0C74DB74FA38D31C035B64A02D6B862C0923DD020A68D31C06262F2E1D5B862C03FC2B236A88D31C0AB036793D5B862C02EE9A27AA98D31C097061D3FD5B862C0DDA30EE5A98D31C036EC2D9BD4B862C0F17A79BFAB8D31C002B8FCB4D3B862C085A4A0BFAD8D31C0DEBB65EFD2B862C029E51FFAAC8D31C0FC34CF6BD2B862C057058051AA8D31C072C6034FD2B862C0A44D2EF2A78D31C00A139E2BD2B862C0171BA364A58D31C0AF87E6E9D1B862C0E5D2E201A38D31C0AB334284D1B862C06B81CE0EA38D31C013ECA049D1B862C0115B530FA38D31C03021C720D1B862C06466D377A18D31C0D9D3DBEFD0B862C0C083E8BCA08D31C03AB261FBD0B862C0F1846DAC9F8D31C041DFD534D1B862C0A37307E19E8D31C0328B973FD1B862C0CB37C1DC9D8D31C0E6900CF6D0B862C08FCED1D59C8D31C054AFFAB4D0B862C09152EA959C8D31C0F5EDDBA5D0B862C0635FD3EA998D31C0F8376C9BD0B862C0130EF68A978D31C09814A442D0B862C0B85B69D1948D31C0F8982F13D0B862C01FAC26A3928D31C03A2A58F8CFB862C056BE956F918D31C0AE8D160CD0B862C093723E038F8D31C031D6ECE1CFB862C0685540908D8D31C0107D10A2CFB862C05AF6DDC48D8D31C004B10E88CFB862C024E777108D8D31C0465A22B1CFB862C06DE9D2AA8A8D31C08A22D3AACFB862C02CE5FB77898D31C09A44F978CFB862C085AEF4D9888D31C0DE254B6BCFB862C00AC38F1C878D31C01FC63158CFB862C000C44061848D31C0D7DDCA4ACFB862C094783FCE828D31C0CF9E505CCFB862C09D026CAF808D31C065D20D51CFB862C0CCA2E96D7D8D31C07857F12DCFB862C04D7F0D6A778D31C078A8E01DCFB862C07185DA5A6E8D31C0F9953D03CFB862C0EB6CDEB0668D31C08D08A8E6CEB862C05E7290F0638D31C0744E7CF0CEB862C05E70186D618D31C0670A3DCFCEB862C019AEB93B5F8D31C083B9F9C5CEB862C047CA39A55D8D31C04DA58CF2CEB862C05C55FB585C8D31C05EF11707CFB862C0C56330295B8D31C03AFD34E0CEB862C00BA5F1F3598D31C0357AB5DECEB862C0DFC56518588D31C026B296D6CEB862C08B1872CD548D31C0DF969AD2CEB862C00F0E10B4528D31C05818CFEECEB862C0CCBFB108518D31C0522D57DFCEB862C03133E2754E8D31C0B3A798BACEB862C0A2F11C2B4B8D31C0EAE3F3B6CEB862C0702DC087468D31C042A2C1C4CEB862C0803E73AA438D31C01C7FE0F8CEB862C06C9D053F418D31C0B52597E5CEB862C0AFA06427408D31C0BDD15CE6CEB862C08863371E408D31C09360ECFFCEB862C08C34D1803F8D31C08B33B735CFB862C0F4A55F1B408D31C0A330B073CFB862C01BA4FF4A418D31C0FD9454A8CFB862C0BEA14E28418D31C05AD5C9D6CFB862C090E3E2AC3F8D31C07B66FD01D0B862C05BEEE5343F8D31C0EC42E114D0B862C0291009E43F8D31C096881C11D0B862C0091E8648428D31C0D6ABD814D0B862C0E0946083478D31C0E3F6E826D0B862C096150207518D31C0B654063BD0B862C0AA5B9FDD578D31C0272FC67DD0B862C08444A25E5C8D31C0DCB37EA2D0B862C0593FDDD3608D31C080E54792D0B862C0F1E7D239658D31C0E320E287D0B862C0B68D0899688D31C096F07255D0B862C0A825C0056A8D31C0FD794C15D0B862C08915EB096A8D31C04E4405FBCFB862C0898568D36C8D31C0BF9B93F3CFB862C031034796708D31C08855A210D0B862C060ADFD4E748D31C030463B4FD0B862C02A6C096B7A8D31C0DE8D1193D0B862C004F8A95D828D31C0A781CCFAD0B862C02BA6D2C9858D31C0366A1E85D1B862C09A9FD5E4888D31C0CFC4C3F9D1B862C0C76DFE7C8C8D31C0E1E9A435D2B862C05AE5550C8F8D31C00665C099D2B862C0D7046DB5908D31C0105DAA2BD3B862C010BF545C908D31C0E649055DD3B862C0C3F740BF8E8D31C02B4CE254D3B862C056F6530A8D8D31C08123BF19D3B862C03A22AFC98A8D31C0BCAED0B9D2B862C00ED33B61888D31C0C867D7DFD1B862C05AAC67A5828D31C0BB19C64DD1B862C0925842CC7B8D31C0C68EE60BD1B862C00F4DE33B748D31C0A7F73007D1B862C0A4660C9A6C8D31C02069D142D1B862C00CC7F5ED648D31C05E707C8CD1B862C0EB2C06865E8D31C04AEA329FD1B862C082C7612D598D31C03C48E15DD1B862C0893CF7C5548D31C09330A0CCD0B862C05583FBFE4F8D31C08C6A30C4D0B862C0B8D164A64B8D31C029BEF60CD1B862C0F7900408458D31C04BA5B27ED1B862C043BADF5E3B8D31C056BB160AD2B862C02CCE6DE9328D31C0EC60389DD2B862C0EC9FF98F2C8D31C0590D1FCDD2B862C02E2B0DC7288D31C083BBF30ED3B862C031076E84248D31C02BBD3A9DD3B862C0D82EA792218D31C07F4D6034D4B862C0260166D01F8D31C0349377C3D4B862C09C01A1831C8D31C09A3C2B5BD5B862C0D0FE3034148D31C0C10D0C8AD5B862C0361139C0108D31C048D500A0D5B862C015451CC50A8D31C0C8040AB5D5B862C071C414F3068D31C08F5489D1D5B862C03BC1AC79058D31C0FE96C409D6B862C0F424DAA0068D31C0DADD6352D6B862C029B44A9A088D31C0C48B3239D7B862C027BDAFF00D8D31C0D8557B15D8B862C0AEC87CF6138D31C0950C93AFD8B862C0BAB32B6F178D31C014DFEF05D9B862C09B1C41761A8D31C0', 599, 597, 6537, 'AHI', 'Ahi', 99, 'Aire marine protégée de plan de gestion de l''espace maritime (Polynésie française)', 'local', 'designated', '2004-10-21', 2004, 999999599, '999999597', NULL, 1, 1.28199765913039, NULL, NULL, NULL, NULL, '2019-09-10', NULL, 'FR-PF', 'Polynésie française', 'FRA', 'PYF', 'Polynésie française'); -INSERT INTO prod."Aires marines protégées" VALUES (9, '0106000020E6100000010000000103000000010000002E0000009CB42C3CB8DA64400068A00EB85336C053F4FA72BADA6440409CF81EB95336C0D0B52D9FBCDA6440102ED177BC5336C0BF1BF1B5BEDA644070087008C25336C01032DDACC0DA6440A02D22B5C95336C04352277AC2DA64401017A757D35336C01437D614C4DA64408E92FABFDE5336C05740EA74C5DA6440402952B5EB5336C06F028A93C6DA6440C05809F7F95336C0D856206BC7DA6440F16A283E095436C08F927CF7C7DA64408184883E195436C0508EE135C8DA6440B1AA6BA8295436C05FFA1825C8DA6440D1310C2A3A5436C0C30D76C5C7DA644071E027714A5436C0BF0FD618C7DA6440BF22A92C5A5436C013889422C6DA6440FF96290E695436C053AD7BE7C4DA6440D07B81CB765436C0DC61AE6DC3DA6440A1C23420835436C0187487BCC1DA64406F22E0CE8D5436C02B9374DCBFDA64404FCA3AA2965436C0180BCED6BDDA64404F35546E9D5436C094FCA6B5BBDA6440EF8D4811A25436C0BBF89B83B9DA64402F5DF973A45436C094DA9D4BB7DA64405FAF928AA45436C04304BB18B5DA644021AB9354A25436C0E812E8F5B2DA644090B20ADD9D5436C02CA5C9EDB0DA6440A09A323A975436C0E8347F0AAFDA64404010218D8E5436C0AFB27155ADDA64406F801601845436C0772122D7ABDA6440902A96CA775436C01CDC0197AADA6440CE327E266A5436C0B8ED4B9BA9DA64405065D2585B5436C0B477E6E8A8DA6440A08C46AB4B5436C089FF4983A8DA64406F52FC6B3B5436C020CA706CA8DA64405F85EFEB2A5436C00032CDA4A8DA6440F030517D1A5436C0B35B452BA9DA6440D05C09720A5436C0D8CC3BFDA9DA644061ABFC19FB5336C058B29A16ABDA6440B029ACC1EC5336C0CCAAE671ACDA6440CF1B8CB0DF5336C054735E08AEDA64405046B927D45336C050E517D2AFDA644080ECA960CA5336C013602AC6B1DA64402EA4168CC25336C0E4CEDADAB3DA6440708505D1BC5336C01F5ECA05B6DA644070E4084CB95336C09CB42C3CB8DA64400068A00EB85336C0', 441, 441, 3868, 'AIGUILLE DE PRONY', 'aiguille de Prony', 55, 'Réserve naturelle (Nouvelle-Calédonie, Province Sud)', 'local', 'designated', '1993-06-26', 1993, 220023, '220023', NULL, 2, 0.125856675843117, NULL, 0.1255810390469, NULL, NULL, '2019-09-10', NULL, 'FR-NC', 'Nouvelle-Calédonie', 'FRA', 'NCL', 'Nouvelle-Calédonie'); - -SELECT setval('prod."Aires marines protégées_id_seq"', (SELECT COALESCE(MAX(id), 0) FROM prod."Aires marines protégées"), true); diff --git a/datascience/tests/test_data/cacem_database/V666.501__marpol.sql b/datascience/tests/test_data/cacem_database/V666.501__marpol.sql deleted file mode 100644 index 45e285fac3..0000000000 --- a/datascience/tests/test_data/cacem_database/V666.501__marpol.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS prod; -DROP TABLE IF EXISTS prod.marpol; -CREATE TABLE prod.marpol ( - id integer PRIMARY KEY, - geom public.geometry(MultiPolygon,4326), - zone character varying(254), - "zone_neca" character varying(10), - "zone_seca" character varying(10) -); - - -INSERT INTO prod.marpol VALUES (4, '0106000020E61000000100000001030000000100000009000000AD0812BCE168E4BFCCDEEA3227BD4840BE63AEABD812E4BF1C5E8873F8AC484044BD156CA117DABF84C0E2AF49AC48408E16A14DE463CCBFBC9F7168A2A5484008BF4C12D0F97B3F9494F5EA3CAB4840399BF9438A28B43FDC4BF050D9BB48404BAA02B73C2CCCBF24A79C8362CD4840BC46F7A9D24DE1BFA0238D36B2D04840AD0812BCE168E4BFCCDEEA3227BD4840', 'Baltic sea and North sea', NULL, 'SECA'); - - -CREATE INDEX sidx_marpol_geom ON prod.marpol USING gist (geom); - diff --git a/datascience/tests/test_data/cacem_database/V666.502__competence_cross_areas.sql b/datascience/tests/test_data/cacem_database/V666.502__competence_cross_areas.sql deleted file mode 100644 index 82366cc912..0000000000 --- a/datascience/tests/test_data/cacem_database/V666.502__competence_cross_areas.sql +++ /dev/null @@ -1,29 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS prod; -DROP TABLE IF EXISTS prod.competence_cross_areas; -CREATE TABLE prod.competence_cross_areas -( - id integer NOT NULL, - geom geometry(LineString, 4326), - "name" character varying, - "description" character varying, - "timestamp" timestamp without time zone, - "begin" timestamp without time zone, - "end" timestamp without time zone, - altitude_mode character varying, - tessellate integer, - extrude integer, - visibility integer, - draw_order integer, - icon character varying -); - - -INSERT INTO prod.competence_cross_areas (id, geom, "name", "description", "timestamp", "begin", "end", altitude_mode, - tessellate, extrude, visibility, draw_order, icon) -VALUES (1, - '0102000020E610000018000000713D0AD7A3D049C0000000000000124014AE47E17AD449C03D0AD7A3703D10400AD7A3703D0A4BC0E17A14AE47E115400000000000004BC01F85EB51B81E17400000000000004BC000000000000024400000000000004BC08FC2F5285C8F26400AD7A3703D6A4CC08FC2F5285C8F26400AD7A3703D6A4CC0D7A3703D0A572C400000000000404EC0D7A3703D0A572C400000000000404EC00000000000002B400AD7A3703D2A4FC00000000000002B400AD7A3703D2A4FC000000000000030400000000000C04FC000000000000031400000000000C04FC0000000000000334000000000000048C0000000000000334000000000000048C0000000000000324000000000000044C0000000000000324000000000000044C000000000000031400000000000C042C000000000000031400000000000C042C00000000000002B4000000000000042C0000000000000244000000000000044C0000000000000244000000000000048C0295C8FC2F5282140713D0AD7A3D049C00000000000001240', - 'SRR Antilles-Guyane', NULL, NULL, NULL, NULL, NULL, -1, 0, -1, NULL, NULL); - - -CREATE INDEX sidx_competence_cross_areas_geom ON prod.competence_cross_areas USING gist (geom); - diff --git a/datascience/tests/test_data/cacem_database/V666.503__threee_hundred_meters_areas.sql b/datascience/tests/test_data/cacem_database/V666.503__threee_hundred_meters_areas.sql deleted file mode 100644 index 064a15b3bb..0000000000 --- a/datascience/tests/test_data/cacem_database/V666.503__threee_hundred_meters_areas.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS prod; -DROP TABLE IF EXISTS prod."300_metres"; -CREATE TABLE prod."300_metres" ( - id SERIAL PRIMARY KEY, - geom public.geometry(MultiLineString,4326), - secteur character(100) -); - -INSERT INTO prod."300_metres" VALUES (1, '0105000020E6100000010000000102000000A71C0000D015879F4A9B4B40D72CD54BCA0A35C03199EE39459B4B40A5A67BAABA0A35C047E366853D9B4B4032C70F6D9F0A35C06E524E71389B4B40681DF617820A35C079406EF1319B4B40F9B0EC574D0A35C0A3A27ADC2F9B4B40D16BBF9E340A35C08DBECBA62F9B4B409DC6BE971B0A35C0BFB57151319B4B4027BF38C2020A35C0F18C6059369B4B4045B00D27D30935C02EA191623B9B4B40E8F956CCA00935C0ACAB4D4B3E9B4B40FC59D43F8B0935C0B61D499D429B4B40C8C3438D760935C00E80575D4B9B4B404E7C574D530935C018E1B9D2529B4B40F3185C9F3A0935C0C7EAB3475C9B4B40D969207D240935C040F26FAD669B4B40ECFC7DC90F0935C0B77320E56F9B4B40953348CAF80835C0A53CD827799B4B40B84BAC9BE50835C026F0FE88799B4B40A9E5374AE40835C055CF64D27A9B4B40B0BD3C81E00835C0B3C9934A849B4B403FDCFD27C90835C01BA69E3B8C9B4B406F1F8223BB0835C0DC053D588C9B4B40FF9502C3B90835C0F9789DBD919B4B404FA613079C0835C0521DB2D7999B4B4097F64B8B800835C0A0D6B637A19B4B40773A5A0B6C0835C0C8621B98AB9B4B40C64CF603540835C00806BC08B89B4B4087F993BB3F0835C0411EF128C69B4B402A9FC9CF2F0835C06351BC3BCE9B4B4014CFE56C280835C0D5A29BB9D49B4B408C58A1F9170835C0FFA387EADB9B4B40F7AD61D8070835C0573B872AE99B4B40940A7778ED0735C03EEE5950F39B4B40B45BF802DC0735C0A0DFF1ABFA9B4B40DEE3ADA3D20735C09A91D300FC9B4B40006E6345CE0735C0726CD7C0019C4B409E9E6E25BE0735C0D75D03BD0B9C4B40C3D27DB4A60735C069F8F1AE179C4B4096E1BEC1920735C0A630EFEE1C9C4B407866C5618B0735C043A874F0289C4B40334836077D0735C0104BD9F4359C4B403D676A38720735C01365A1B0439C4B4061BC0D346B0735C008FC29D4519C4B4062FBD022680735C0C6DC2A18589C4B40AA71288E680735C01F165902679C4B40F778A53D490735C03475FD026D9C4B40B802E4BB3D0735C0CB0DFA84759C4B40B84C0FD72E0735C047225821809C4B40E078C4B71B0735C0A4A2A62D879C4B40CD1B0EAC110735C0C85CBC68889C4B40DB1624EF0C0735C0652F32F2899C4B4094C7C908060735C0A28DA77A8B9C4B4070E95F6C000735C01C6E62D88A9C4B40DD1D7E7EFC0635C09DBFBCD3889C4B40E9BEA123E00635C06B758742899C4B4045C02E8BC30635C03612E1218C9C4B40D9692273A70635C0A99EB25E919C4B40398524968C0635C0CB9F2ED6989C4B407C48AFA6730635C0586CB856A29C4B40439F6D4A5D0635C01A9AD87DA59C4B40D52D8FED570635C049FA682CAC9C4B406003CED2420635C0FC142559B09C4B4086AFA4C6360635C009F59AA0BC9C4B4039F0D94B160635C01E470256C99C4B4017399C97EA0535C095E90CD1CB9C4B4002573A91E20535C0B8431A31DB9C4B40E25859B1B30535C0F40B1031E89C4B40C2A9C67C940535C0CD770ED1F99C4B40C442E2BC720535C0DDB89257FB9C4B408662EEE16F0535C0AFE88F370C9D4B40AE380802510535C0C6CDBF9A199D4B40CB157FA13C0535C0ED7727BD289D4B401F7C8C272D0535C02E351B1E399D4B40243CC417230535C08314E222399D4B4099DAB615230535C027664D5C3C9D4B40555C670F1F0535C03E534F084B9D4B4013B38212100535C0166E33E15A9D4B40ABA6672A060535C064B61541759D4B4039E9706AFA0435C07FE13C4A799D4B40D483ABCAF80435C0E266511E829D4B40A690979DF50435C0BB6E1F36879D4B406171485DE80435C0DF4EDD52879D4B40ACC7B53BE70435C0874D76998B9D4B40DFA17D75CF0435C0E9157B068C9D4B4032C896DFCD0435C042870E668D9D4B408491BF2FBC0435C0603D9935919D4B40148F11F5A30435C0BF33FFCF969D4B4008E623FD8C0435C05F0237189E9D4B400CBAFABE770435C0D973FA8E9E9D4B4053136795760435C01651D8DDA89D4B4092C796C0600435C0C2296EF0B49D4B403F6DB5624E0435C079764077BC9D4B4087F93D5C460435C0833EF818C79D4B40E2898ED5340435C0A4EA9FB6C99D4B40832C4B2A310435C01B49E568D79D4B401BCE812D210435C01FC284F0D89D4B4060D02903200435C0AC016B15DA9D4B404643B3391E0435C0A612A6BBE59D4B40548F77AB0E0435C05FE8FFF7E69D4B40345514800D0435C05FE39055EA9D4B409BD7F880080435C00C135B82F89D4B404C35CC06FA0335C029B0DD29FB9D4B40B2BC16D8F70335C059D9F6BD089E4B4093693BECEE0335C0A656A0E60B9E4B408BD62ED1ED0335C0739FDDD4149E4B408B86D63CE30335C00A34BD8E159E4B400476C984E20335C04EF21DF8229E4B40EDC582A2D70335C0C378111D319E4B403102BDC2D00335C09BAB26A73F9E4B40CEFF7A0FCE0335C03273823D4E9E4B400B773D99CF0335C0D904B3DB519E4B40929C550DD10335C07767585B529E4B406E013EC9D00335C0DE82C0B0609E4B40DDB64D6FCD0335C01933EE206F9E4B401EE0E838CE0335C0541A3653709E4B40094CCF75CE0335C0FC4FE9337E9E4B40A6CEDC34D30335C0C9AAD58C8B9E4B400B2CE3C6DB0335C0FA8EF111989E4B4072A30FFBE70335C0E3D7E97BA39E4B401683DC8BF70335C08D22B889AD9E4B40E0259D200A0435C04D3B1502B69E4B40520B774F1F0435C0D73A1DDEB69E4B4082E5F0D4210435C094920D81BB9E4B40FEF279A6270435C06CFCCD4EBC9E4B40CBDF9275270435C01CB52AD7BD9E4B40F0E74A56270435C0F30B5D77CE9E4B404689B8C3280435C0BE4748FED99E4B40D3B91CA62D0435C012D9282EE59E4B40BAB153E62E0435C08CCB5E80E79E4B406C97EEE62F0435C08C78E4D8EE9E4B404FF51C4C300435C08E753098FD9E4B4057FC998D350435C02D5C7660039F4B402C4D2C79390435C00D75346B0E9F4B407E130BB93E0435C0482DD9F50E9F4B40E0B1FB173F0435C076AB9F32119F4B400508AADE3F0435C01E6E1711149F4B40AC762464410435C0A1388BB0189F4B40197B9715440435C031CC718F1E9F4B40D21EFCD0470435C073A96DEB1F9F4B40D92BE37F480435C0D047DC68209F4B40C4D68EBB480435C07A687578279F4B4008608F88490435C0F02A846E299F4B40B3EBA721490435C0EF9B12FA2C9F4B404ACAC6C5480435C0B68609D6329F4B40A2268405490435C0A2E878533D9F4B403BABD9CE470435C0CBB650FC3F9F4B40F008BCD9470435C02CDAA1BD419F4B40F178305F470435C0EFFBD316519F4B4046F030E6470435C072881994529F4B40A507B52D480435C049F6C4B7619F4B404A20CF614D0435C0DA300935709F4B408144F020570435C06146A3A97D9F4B4056A8FF28650435C0822EB701809F4B4064C080A5680435C0F6C374DD8D9F4B405D3B79D56B0435C0DA1A395B929F4B40FC99DEB56B0435C0B77C98B6939F4B40992B77A06B0435C0A72C4DAC989F4B4047BFA093670435C06FF7F606A49F4B40E08EEDE15F0435C0FA42FF0AAB9F4B40FD06240F5C0435C0444E1088AD9F4B40CA454E9C5A0435C0C8EE169DBD9F4B40002296F44F0435C07161F646C29F4B4043AE28BD4D0435C016350691C79F4B40D68C5DDE4A0435C0D1C3A9FCCF9F4B40FAF49DCD430435C0BC5D9C51D09F4B405ACFB486430435C0F0312AA2D99F4B407EAE76CA3B0435C0F0220F13DB9F4B4007715A413A0435C01529AD70DE9F4B4078E382AB360435C0A82D2EE3DE9F4B4013474E32360435C08D67C43BE69F4B40CCDE43782E0435C067D56577ED9F4B40225CF1B0260435C04858D396F79F4B40F807DF501B0435C056D8816FFB9F4B40AFA7E590160435C0E6FF9C1501A04B40AA53F6690F0435C0C7EE037B08A04B407AEFB4F2060435C0FDAC9E3413A04B408D2E62E7FB0335C02267ECDD17A04B40C2861E6AF70335C0E7297AA91FA04B40E3F78669F00335C0308E7C9420A04B40099D5299EF0335C065839F4B25A04B4076CAB47AEB0335C01A0BECD226A04B400A874CB1E90335C047FD847033A04B407C94B150DD0335C00936C8E640A04B40DDE150A2D40335C0F4C711EB48A04B4031F9D5B3D00335C0A6D034B54CA04B40718CF3FFCE0335C01D164F274FA04B40D02F2200CE0335C0B3DF247B4FA04B4021F99AA4CD0335C02ED6490454A04B40D957930CC80335C045E27F585DA04B40027CD94BBC0335C0B48FFE7261A04B40093AE601B70335C0148335736AA04B40ADCCF8BDAC0335C08FE0C3586EA04B4070E197D6A80335C01E36CFDB82A04B4053A622C0990335C08475B9B889A04B408D18895E960335C0E890112894A04B40366F5D64920335C092F2BBFC97A04B4016717E59910335C0CC124060A5A04B40A903D8E4830335C0B393ABBBA7A04B40FF580B93800335C09D886196B4A04B4017E8DC56710335C0BA0D201FBAA04B4024792EE56B0335C0BFC2D959CDA04B40BFFAF80C570335C0A08766C6CEA04B40D24AA889550335C06BB96BE300A14B40D9D41C5D210335C09CFECAEE4AA14B4076DC646BCC0235C0ECFAFAEC94A14B40A769D9B9710235C07FE2F64DECA14B4008869511FA0135C0ADA66B1739A24B40D8446A04850135C08BCDD14B9EA24B403F449065D40035C08EA21DA7B4A24B405A17EB1CAB0035C0DFE44C39BBA24B407983B7B39C0035C031993F59C6A24B4023799770800035C01691E45CCDA24B40DF7DFCC06D0035C0ABC523BDCFA24B40C1345BB3670035C0046AB51ED4A24B40842A19055D0035C07DFD9A71D6A24B400203FA45560035C044CCCD01DDA24B40497C4E78450035C0DABFC3E1DEA24B409426B637410035C0BB0DDA02DFA24B40CA9357D9400035C0BFDFF885E3A24B404C98035B360035C070AE2AC1E5A24B40E3B291262F0035C0090C2599EEA24B408D3525751A0035C01EC8E4BBF0A24B40F1516729160035C025A5E0B9F2A24B40E98628C4100035C0AAEC5A13F6A24B40F742B131090035C071D890FDF9A24B404F75F333FFFF34C0123679FEFBA24B400824CEFFFAFF34C01D39AE59FDA24B409A741499F8FF34C0D0FB0EE800A34B40B9DCD24AEDFF34C04584426D05A34B40104D49FBE2FF34C00EBCE75F06A34B40A0246F48DFFF34C0C35488FC07A34B40A4DAD022DAFF34C0F65459FF0FA34B40E997D76DC4FF34C026EE64F513A34B405759443DBBFF34C06658AC2615A34B40D2399ECFB8FF34C0189830AB19A34B4012B2B781AAFF34C0C36DBC6222A34B400C64EF5596FF34C01EB755E024A34B406EAA5B5D91FF34C07710FD292BA34B409F21631E82FF34C023748BE12EA34B401C52A60979FF34C0FB8C02A837A34B40FD332C8162FF34C05D98835C3BA34B408A35A56556FF34C0A100D22B3DA34B4022916E6E51FF34C0A4B95AA93EA34B404DC82C6A49FF34C02AF1050D44A34B400E8CEB2935FF34C0A2CF7F1447A34B407E82A8642BFF34C0D9A3072447A34B40EDFADF242BFF34C01B780D234AA34B407DDA027516FF34C0BEEEC65450A34B401F2DF0BBFCFE34C0E78F56B353A34B4055441928F3FE34C0E233FBC055A34B4029D759B3E8FE34C0EE10170E5BA34B40888C5605D3FE34C0EDD4606E5CA34B40DB272658CCFE34C0C554BDCD5DA34B40C894F27DC6FE34C03ED737D55DA34B40FE502D47C6FE34C0AD1AC49960A34B40F43ABFBDB7FE34C0AC15330361A34B403B753DB6B2FE34C07FCCDB6C66A34B407A1360A696FE34C05FCEACAA67A34B40B323937D8AFE34C0265DE7B26DA34B40FA19790C6DFE34C0DB20F5096FA34B40BCB4C0EB67FE34C07726253971A34B402943E32B59FE34C0FEB7A7AF74A34B40F81A401146FE34C0245E532475A34B404B567BF443FE34C03FE3D49A76A34B4064C9C4422EFE34C002D7D6EC79A34B40EA697C8C12FE34C034BD6F517EA34B40ED213871E9FD34C0693895957EA34B403599A309E7FD34C08206869981A34B40E2BCAEB6CCFD34C00CA1352484A34B4045E16763A4FD34C0C52B184984A34B403E579137A2FD34C04779417385A34B40CA57558191FD34C0B9A82AF385A34B409E46C16182FD34C0D632794F87A34B40452AE4BD4EFD34C0F85C76A288A34B40D6337EA538FD34C07719FBC68DA34B40A1EE86EE01FD34C0AC6C4C7B91A34B401F73B8E2D0FC34C074F4518C91A34B40D1904505D0FC34C01747E06C94A34B404FD7AD3AABFC34C0DC727ECC98A34B40214EDD8A73FC34C0621E79169AA34B40E47BB9BD54FC34C0B98244609BA34B400EBB565642FC34C0C39AD301A0A34B40C5C3235713FC34C02E198A74A1A34B40CCE55E1507FC34C04ACB7931A8A34B40901B4B47D6FB34C0987175E2AEA34B405BFB311C96FB34C07EC6C506B4A34B400918A58C5EFB34C04A423CF4B7A34B403DC4827B30FB34C0E312FB52B8A34B409501E3702CFB34C0CA6DFFECBBA34B40B75480A807FB34C0935F03E2BDA34B40A11D09CCF7FA34C0F7112618C5A34B407EB0B987C7FA34C01B102B77C8A34B40C387E15EAEFA34C0050DCF70CDA34B40BBE6687F74FA34C0495557E3CEA34B40AB2B580C60FA34C0A51AD239CEA34B40B3331F3637FA34C0F6802732CEA34B403CAD2DC32EFA34C0FE329B86CEA34B40138EA6990AFA34C0D949BD88CEA34B40F20B30CB09FA34C0D8605F02CFA34B402EBE5CA0E0F934C0ED6A7619CFA34B40F3827CD1DBF934C0901170E6CFA34B4052BACC00BDF934C01D054307CFA34B405841BD53A7F934C0475CB128CDA34B401A3536958BF934C0F083EF0DCDA34B40A981149085F934C05C191099CCA34B40C34B8D8B7FF934C0D0D719CCCBA34B402F2387A95CF934C023987615CCA34B4007E5B3F352F934C08CEEAD0ECCA34B405BFC78BE4BF934C073541714CCA34B406C623E9146F934C0DC6DC820CCA34B40BE56C96142F934C0574E3BA5CBA34B40AF868CB63AF934C0241A7B12CBA34B40ABE7E7302DF934C006F634DDCAA34B404DC40EB923F934C0B2CD8EA0CAA34B40748FD8EB21F934C0134A9A5DCAA34B40013F1CE01EF934C05DF97F0ECAA34B40AF8A15051CF934C0EA88D9DCC8A34B4097FC997404F934C07AFFF484C8A34B4056442DBA01F934C05CC8881AC8A34B40DADF2545FEF834C076C00969C7A34B40B1CE5839F8F834C07FF14B6AC5A34B40212A29F3DBF834C0297255B3C5A34B40E004537AC9F834C0DBADA6AAC5A34B40A3027ADCC7F834C05EA060C6C4A34B408A063335C0F834C07FA82EC1C4A34B40110BA6F3BFF834C0701F4173C3A34B4032A6FA7DB9F834C0C71A1E6CC0A34B40B8FD4B2A9EF834C083E042E1BFA34B40163AF02796F834C0C06A6FCFBEA34B402AAC330490F834C0F09DE933BDA34B405821A26083F834C06B915B72B9A34B40E5CD388872F834C0939CF291B7A34B400ECBA9CD67F834C03DDA1708B7A34B4044A0B30864F834C0C0E31C3EB2A34B407394161E51F834C0076A4424AEA34B400A47BAAF37F834C03E33E514AEA34B404888232E37F834C0D0EC45E1ACA34B409887BFA732F834C02FB8C1E8A6A34B4038FC6E4F16F834C073F29F94A2A34B40D44E7F25FBF734C0BD5EC95BA0A34B404FE441BFE8F734C0199B247C9EA34B40410756B8DFF734C00F0336579BA34B40BEAF51FBC4F734C0CEAD67229BA34B40182E973CC2F734C030FDF80897A34B40B44DCBAAB0F734C05D5A4D7893A34B4071FEB8C398F734C05717E65192A34B406C0C05488EF734C09CC613C291A34B4041102DC586F734C083BF4B288DA34B4023147BBF78F734C0BD6AEDF786A34B4049C5A8F25AF734C036A27A4785A34B40E04E72FC4FF734C0A1ABE34385A34B40E0CDDCE34FF734C003499A4784A34B400874E3514CF734C0AA6E7CCB7CA34B404808903E34F734C0BA4D766E77A34B40102C7D2D20F734C091E38EB675A34B40CA340F9618F734C052EFB1B373A34B40F3A611E211F734C0847D7B1171A34B40DC219A5B0AF734C07D7EB7646AA34B4006FD89E4F3F634C023A6BEDF61A34B40589C0498D1F634C04F43A3645FA34B40059F7EAAC6F634C0E9B28C545CA34B40F0E31BD1B7F634C032BA296F5BA34B403B7F7C4AB3F634C0E8F57C9A59A34B409B1CF8A4A9F634C0C1FEC47756A34B4070B48BF49CF634C0F4AAC12450A34B405E6B6FF778F634C039706DBC4FA34B40E9125E1B77F634C0CB96BE234DA34B40ACFA84006BF634C04871BC634BA34B40414DB52C62F634C0D667E0CE48A34B4047C4150454F634C016C47C0948A34B401C2466994FF634C041674EB945A34B40992A15BD41F634C044C6CA0544A34B4066DB825839F634C0EAE7419843A34B40A4D6F94637F634C04ECA5A9A42A34B40B334AFCB32F634C054A472C83EA34B40D89783FA1DF634C0E776C01A3DA34B400D90AB070FF634C00D3EF8123CA34B40B92358630BF634C05E99026C34A34B40854BC047E7F534C060CCB6BD31A34B401CAF7307D5F534C0BF5D4D2830A34B4043A2557DC8F534C048D0B7662DA34B406092FC7FAEF534C0FEDB28042DA34B409B9AF6A5AAF534C02197A09729A34B407120E32D86F534C0FFEFA6CB28A34B40CB4A12F77BF534C08E7BA24328A34B40CD05866E73F534C0AEB6AB8026A34B40012454205CF534C0BB0A88B825A34B402D7E73D454F534C01074F16324A34B407AE82E8944F534C03B7B801D24A34B40B2F1C3AA3FF534C00E71C87423A34B40D67E476C38F534C00A30AAAC22A34B40D78BC2F72DF534C0BD03E5F721A34B406BD5CAEF21F534C0C59E5D9221A34B40A2022A3519F534C038B9E20E21A34B40C4DB034609F534C0A1E8DC7221A34B40485E632EEBF434C0AF9B9B7921A34B40F160D8BAEAF434C0062F246D21A34B406430CC5DE5F434C04C701A6D21A34B401A348557E5F434C0DBD158E61FA34B40044C358DD0F434C09078484B20A34B40418D19B2B3F434C0D76B2D9021A34B40B85BFB809DF434C02AB32C7322A34B4050A704E883F434C083AEEF2D23A34B40AC6A149A76F434C0D34E578925A34B4038B77C4057F434C096E226AD25A34B404EC7217555F434C0865F030628A34B4072ABEC6238F434C0FA1461B528A34B40B7046EF730F434C01AE8F34B2CA34B403FE76D760EF434C0FFDBFE6D2CA34B40EBA039350DF434C09531085F2FA34B400D1954F7F1F334C0D469B8B02FA34B4056EA4521EFF334C0A9C2420534A34B40754B1623CAF334C0461251C234A34B405E73CA4CC4F334C009EAE26937A34B406B1710CAB0F334C0FFF6280E3CA34B406802171097F334C00687C1A342A34B40F10CD0137AF334C0D9872F3C4BA34B40F5C23F6B5CF334C0844A01EC56A34B40A63962E53BF334C0E5A0162F57A34B40682EB02B3BF334C022BE69E459A34B401490A1B633F334C05D73376C5FA34B40C724EAF725F334C0089A9A8966A34B406BA1A6F615F334C02596F56F70A34B404BFA80D202F334C0452ED20578A34B4005024E35F6F234C0D068754580A34B40DDB6A519E5F234C020C5D16380A34B409F564DCFE4F234C06DD7169282A34B40A5231FE7DDF234C063D5229287A34B409EF1981FCBF234C0C925F0C88AA34B4074E1219FBEF234C026C7DC5D8CA34B40264CE9FCACF234C0BD8553638CA34B40A6BAB2B1ABF234C032CE7C498CA34B401B60357FA4F234C07B6957C18DA34B40E83A5C4C80F234C030784A8F8EA34B40E7F81EEB77F234C096ECE1DE8EA34B40647F097071F234C09D7020FE91A34B406DC8207A51F234C0085CADEC94A34B4091D4140D43F234C04B082C1E94A34B40C5E9F71230F234C0B44B75F093A34B404DA861F018F234C0520E080E94A34B4012E33EA914F234C052F21C6493A34B40888E58AE0DF234C01943A61D92A34B40A08DEE8703F234C0F5FF060091A34B4014CD6CF0E7F134C03B0DEC2D92A34B4033A94E5BCCF134C0C096FF9F95A34B4027610874B1F134C063803F4097A34B40CCA0D80CA8F134C0E2BDEC439DA34B40024728118DF134C0CB118AFDA4A34B40721782FE75F134C03AF07BBFA5A34B4026B96DA770F134C04EFE7846A6A34B401AF6423E60F134C00CCE1EC0A8A34B40FC40F4BC48F134C0B20459B0A9A34B407F9AC79843F134C0ED5068B7A9A34B401822F41943F134C0F25E67F7AAA34B4041D4429C31F134C0D722FCC9ADA34B407B753D4818F134C09852FA41B0A34B40A7804E1C08F134C06F522D4EB1A34B409C24510FF5F034C05C882D72B1A34B40E034F196F3F034C05355D0B1B1A34B40F2FC0E3AEEF034C0A38396BAB1A34B402D612281EDF034C020CD7D3BB2A34B403CA8AC21E3F034C0686E0FDEB3A34B40DB3C33B1CEF034C0DCA2ACA4B4A34B408056E4C3C7F034C0D62757D2B2A34B40D99EA15EBFF034C006D90C55AFA34B40EAB57361A0F034C0C7C819D4AEA34B40A6674AB880F034C0EB872B0EAFA34B405757478E79F034C0CA068EC5B0A34B40C5284CD460F034C0DA642D7BB1A34B40D2BA58045CF034C07717D478AFA34B40E23BC16750F034C03BE2B395ADA34B408D9E38A440F034C09ED4FAFFABA34B405A10CDC53BF034C0AB90817FA6A34B40D8EAF03E23F034C024455302A3A34B407AE8117309F034C0B2B67F9CA1A34B4046068CF6EEEF34C08A860B8EA1A34B40F030A782EAEF34C0C2898762A2A34B40152D536CCEEF34C04D4BA690A5A34B409F2842EFB2EF34C02DAAF803ABA34B4087260ABC98EF34C02A177A99B2A34B4050E8F97A80EF34C0D1A07120BCA34B40C3CDDFC76AEF34C0D800AB5BC7A34B40F308212E58EF34C09D692ADCC8A34B4045C9AB1056EF34C022EB6F5BCDA34B4048588A1A42EF34C04761D998D5A34B40F5385E9D29EF34C0EC08B9D8DFA34B40CF1021F813EF34C07CCC4D5AE3A34B40529A4FA30EEF34C08A4C5A7BEAA34B40E17D242E02EF34C0500CA684F2A34B4030F1C2ECF7EE34C07885CB1CF5A34B40BCB1011EE9EE34C0B7C7C7C3FAA34B40A9289665D3EE34C0B5E45DA9F9A34B4039004A0BCEEE34C00AB6D194F8A34B409CB49E88C8EE34C08471AC0DF6A34B4005AA4EF3BAEE34C08D75C72BF2A34B407980E9F19EEE34C0E82BD6C2F0A34B40FE0A632082EE34C0D451F9B3F0A34B40BF1E6D5277EE34C0A4143665F1A34B4060D8DEFF5FEE34C0F9688575F2A34B4069E8A4A24FEE34C0C3645678F3A34B404B17138E43EE34C0D249B7BDF4A34B4091FF242A37EE34C062EDEB09F5A34B40C5C01B5F33EE34C0A80F6A4FF5A34B4040DEE03E2AEE34C0535FDFD2F7A34B40B8470E3312EE34C0D889BCD4F8A34B40CE2B5F2B0BEE34C02CD38259FAA34B4047C5E03003EE34C0A116E9C1FAA34B40AAD5EEC3F0ED34C090C0D605FBA34B40B6675907EBED34C06F7A6667FAA34B4083A409A7E1ED34C0E3D76963FAA34B4017C25021E1ED34C03A2AE7C0F9A34B40305757CBD7ED34C06AB84EA5F9A34B405D5BCBDDD5ED34C085DFCE36F9A34B40D6021467D2ED34C05947A32DF9A34B408005BC42D2ED34C0EBFC7A1BF3A34B401ADA33DEB2ED34C0B5B7B545F0A34B406086FAE491ED34C0E0BB9E1BF0A34B407EAB38388DED34C0C8C884E9EEA34B40E5699BE180ED34C076FBE314EEA34B403EE3C79D76ED34C0FCF0FB1DEDA34B407F6FB6CA67ED34C0FB3764E3ECA34B4097CC619040ED34C05D5FD2ACEDA34B409C6C60C931ED34C04557B1A1EFA34B403EE2BF7C1BED34C0D86F5D14F3A34B406865E4D505ED34C0E2DCCBDDF5A34B4004EA39AFF7EC34C0AC5A5DE7F5A34B40EA8EB97EF7EC34C0AB95A17500A44B409C74D914C2EC34C0587B0C8C08A44B40955579E4A2EC34C0C1F31FB513A44B4079A9F04087EC34C03FD2B0D51BA44B40460F64C276EC34C097CBCABF24A44B409D512CE066EC34C0F7D74EAF2EA44B4032AB944C59EC34C012A1E3CA44A44B40F454170A3FEC34C0E535426971A44B409046057907EC34C0A028242B73A44B40A3A81E5605EC34C0D808673380A44B402B1028E0F5EB34C0A4AFBF9C8BA44B405CA85350EAEB34C0FC6C27519CA44B40BA483413DCEB34C0F800E7FBA8A44B40D6C0F044D3EB34C063686AEFB4A44B40D76035BECCEB34C0FE3A8A24C1A44B4036F4C1B1C7EB34C0C2BA1999CDA44B40A50DA8B7C5EB34C02CE66DC7D1A44B401A977818C6EB34C0D6F87DA6D2A44B4057CADB64C4EB34C013ACD669DDA44B400D28E2AFB4EB34C0A9BD4B44E9A44B40E10B680DA8EB34C08BA3C3F6F5A44B40EBABAFC09EEB34C0D4855300F9A44B4018AF84FD9CEB34C07495D8A406A54B407340E31C97EB34C085E9529914A54B4062D81D1695EB34C0AC20658F22A54B40DFCA95F496EB34C073E1A83830A54B401661CCAD9CEB34C0990A67483DA54B401FC39D21A6EB34C03E70467549A54B40CA69F51AB3EB34C0CEB4E77A54A54B405024F850C3EB34C046574F3B5DA54B40F49D5E47D2EB34C02A82123E66A54B4073017AF7E3EB34C03F23E0C56DA54B4018E8F3F3F4EB34C09F699D4F70A54B40A31F76EFFAEB34C089E2E46F72A54B40CEACAC2C00EC34C0A7EBBF7E72A54B401115C93E00EC34C057844E037EA54B4058016B560DEC34C0A999161E9AA54B4034768D3428EC34C05372239C9FA54B40B672BEC02CEC34C004C129F8A2A54B40893B82B12EEC34C0AC661A2FAFA54B4087C4D48737EC34C0B0A6B6A1BAA54B4088A74D7443EC34C0112D31C2BDA54B407B29064447EC34C0E2DBF15FBEA54B4027D737EE47EC34C0DDABA887C2A54B40490E89104DEC34C0B5FBDD1BC3A54B4052671DB44DEC34C07BBB7517C3A54B40C0F136C24DEC34C0E0CAB6E7C5A54B40D045273C51EC34C005793BF0C5A54B40035B3B1F51EC34C0CBCD567BC8A54B4071AD61C453EC34C02FF9E100CBA54B40793233F655EC34C0BEEB8579CEA54B40EF38702559EC34C0932F3331D2A54B40C2043BBC5CEC34C08219CC8CD2A54B40D1F196145DEC34C06C863F11D7A54B4059ED4AFD60EC34C00E40CF03DEA54B4054DFEDC868EC34C0C1C1590ADEA54B40FF194EAE68EC34C0D5801AB6DEA54B40C0364D4369EC34C0B9C152C2DEA54B402B716F1969EC34C06AACA2B9DFA54B408ACF51186AEC34C0A0AF8230E2A54B402453A8836CEC34C01674C12FE2A54B40CD575F866CEC34C04E329936E7A54B401498476771EC34C0D3C3FA4AE7A54B40EE00427D71EC34C0FFC74DCAE7A54B40650F41E971EC34C032B7F279EDA54B401742A41576EC34C061437D91EDA54B40BB53F22676EC34C0243EB783FFA54B40384E4F5D83EC34C0AFF8838B0CA64B40930AEEC08CEC34C0B35ABC6B0EA64B401CDF0B268EEC34C04BC4F79217A64B4020662E2B95EC34C0119AE3D31CA64B403CEA126E98EC34C0DC9428F821A64B403D6F5DEF9BEC34C06CB769562CA64B407D33DFA5A3EC34C0A019A3F239A64B40657D9C3BB0EC34C0D124A7AF3DA64B4088F3CB6BB4EC34C0B22F7EE44DA64B40FFBF082FC3EC34C078305B4956A64B40D5131BFFC9EC34C0942F42156DA64B40E2D8C1E7DBEC34C091B14A6280A64B404C955532EAEC34C08586A93F92A64B40DDD8B3E6F5EC34C0C7571DE1A3A64B40932CF6A200ED34C06C082CE5B4A64B406F9C638506ED34C0634D59CDBBA64B404A14746A09ED34C0566F2974D4A64B4014A6AF9515ED34C065D88544EBA64B4083E2281220ED34C07F4AC1CBFEA64B407F02922027ED34C07294C04211A74B4035A65FA52BED34C09955477A35A74B408CDA3B1D33ED34C0817AA2A436A74B40638F475E33ED34C0C57855D051A74B40D893F79C39ED34C008D2CF9B5DA74B40123375A93BED34C0702FCA4C6BA74B409E13922A3DED34C07F71FDEF72A74B40C6BE7C793DED34C0467B39047DA74B404EF6FF5D3DED34C0AC7CD0B47FA74B4040A2C38D3CED34C0F9D880F98CA74B402A1712973AED34C0E6C246AE8DA74B40D196A86B39ED34C01743823390A74B4003A8730636ED34C0507B9EEE9DA74B400A5622A726ED34C076E560D8ACA74B40D9562AD91BED34C04B799783BCA74B403376A8EB15ED34C08F341285C0A74B40CD75000F15ED34C0C7F5E1FDD1A74B40D43CE95A14ED34C04E48191ADDA74B40538FE5D715ED34C0DF211827E2A74B403C88C26F15ED34C0CDCAE9AB29A84B40EEAB5CC109ED34C05CA6C1D52AA84B409709319409ED34C00C7C1F684FA84B4049F98A7504ED34C09EE600B571A84B402F257AC8FEEC34C082388DD87DA84B407229577AFCEC34C01A39C23A80A84B402E5CF04CF4EC34C021F73CB18AA84B403831BFE1DBEC34C0C2D4F64697A84B404200004DC7EC34C009A5A2669AA84B40A86A7C0AC3EC34C0E83AC2B2A7A84B40E1CA07E1B3EC34C00CD39423B6A84B40DA345605A9EC34C0A80AF854C5A84B40F894B2C2A2EC34C03CC5D8A9CAA84B40E4734C6AA1EC34C0EDD6EBC2D7A84B408A3890D69FEC34C0A7826DD9E4A84B405FEAA3AFA1EC34C0B06918FAEAA84B4051ED3730A4EC34C0DCD81B42F1A84B40D0D1DF2498EC34C058778748F3A84B4095AD64DB94EC34C039B4C97201A94B401D0E6FD881EC34C0812D913A11A94B401A2FCC0974EC34C0C21FE3A814A94B40AED560BB71EC34C0A72218F721A94B4079EEFBCC6AEC34C012A689AA2FA94B40D960289667EC34C0CB608C8033A94B40A449CE3767EC34C034B645D641A94B4037AA34E367EC34C0F0D2F4F34FA94B4034B3E89F6CEC34C0C18868865DA94B40A282FF5175EC34C09A12A43D6AA94B404CCE39C681EC34C0B4F527F76DA94B40F2F6A02086EC34C02F11995C7BA94B409787571399EC34C004DFE1C386A94B4026688A2CB0EC34C0E169937A89A94B404953BAC1B6EC34C039983EFB8DA94B40DC88E1A8C2EC34C09D1FFE5BA2A94B40A3363D83FDEC34C09B17CAECA3A94B407E640AB301ED34C0DC02160CA5A94B402430BF6D01ED34C0C4A46403A8A94B40E4714DC0FFEC34C00F54C2B3B8A94B40CEECD21DF8EC34C0ABFFDC84BDA94B400F025229F6EC34C0079F27AEC3A94B40C48990F9F3EC34C0496B4106CFA94B4085FD0DFAEEEC34C0FC29C0CCD2A94B406D342D77EDEC34C069CC3EF7DDA94B40AFA02471E9EC34C02DD3EB2BDFA94B40E228CCDBE8EC34C01181891FF1A94B403A621890E3EC34C00FE2B53FF3A94B40F9F73F53E3EC34C01DF9CD81F6A94B4072FCAA07E2EC34C0B468494BFDA94B403074D7AEE0EC34C0DE82BFCEFFA94B40D562713FE0EC34C0DDD8EE2A01AA4B408D0CF70BE0EC34C0EC80C04C03AA4B402B14DC42DFEC34C0FBA96EBF0EAA4B4095A0F93ADDEC34C09EE78CAC12AA4B407806DDD3DBEC34C03B54001B13AA4B40693138A6DBEC34C053D6D77315AA4B4074209F41DAEC34C09220CF6517AA4B40F205CDBAD8EC34C062E8414A1EAA4B40DA0838E7D3EC34C0899EC9FF23AA4B403092D25FD0EC34C0ACBC56DB27AA4B400E1AFD28CEEC34C0AD09B29C2BAA4B40A89B122BCCEC34C0153981052EAA4B40B93F750BC7EC34C00ED1B46B38AA4B40812B5C7EB6EC34C0FCB1BF3B39AA4B40E15BFA60B5EC34C02ED5E35C44AA4B406A1E0F3DA8EC34C08D73D95A50AA4B4046A17A209EEC34C0608A3EFA5CAA4B40142F4F3D97EC34C08E0B146C60AA4B40E07B084E96EC34C0D1F40A9867AA4B40968886CC92EC34C095DD5C3E74AA4B40D73BADBE86EC34C0FC0230D878AA4B4009532C4683EC34C0BA14C7487BAA4B40900C7EA980EC34C0B5625CF47FAA4B40971990FB7BEC34C06A1B12FC86AA4B40D818326875EC34C0D1963BDC92AA4B405B51B5226CEC34C08A1BB94B9FAA4B4066F6BFFA65EC34C0C861FC63A4AA4B406A2EAA2464EC34C0F99529A9ACAA4B403258A0DE61EC34C048AD3CC7B4AA4B408EB9BE5260EC34C0AF743E25B6AA4B409A13E41460EC34C007CA13BFB9AA4B40AE7BAB7E5FEC34C04B2AA4D5BBAA4B4023679C5F5FEC34C0C3DFF211C9AA4B40AF683AA059EC34C014184978D0AA4B402B923EB058EC34C0C09E5F66D3AA4B402392943357EC34C02A780405D7AA4B40D4B5315D56EC34C06E1636DDDDAA4B408384A15C51EC34C0F6B30298DFAA4B406D09B3C34FEC34C0172FFBC6E6AA4B4010B529D049EC34C0F183838FEFAA4B400B22535743EC34C02E4CAA0DF1AA4B4079915AA341EC34C0310E18D5FBAA4B40598FF3E635EC34C02C157EFE03AB4B408B2635FC2DEC34C09C7C923308AB4B407E3614612AEC34C01CD496690FAB4B40C5360D2723EC34C0314865F51CAB4B40AD86E30918EC34C0F8A540B71DAB4B405EF1BB8B17EC34C028D8BCAD1EAB4B40767C789916EC34C072EBB1DD2AAB4B4000D3D19A0CEC34C0DD123E5530AB4B40527246F508EC34C0674C1BFE32AB4B4033E8674407EC34C0F15E926F37AB4B40FB0F759404EC34C0330EEEB245AB4B4070DBB389FAEB34C013088FAF47AB4B409CB1BB2FF9EB34C0239C714B5EAB4B40004CC157EAEB34C0A51690F763AB4B40B52BC7FDE5EB34C088B3814772AB4B406C548F03CFEB34C04994D3F474AB4B40DB2890DFCAEB34C0EA24125F7BAB4B4050090951C1EB34C04A4C273081AB4B4029CBA253B8EB34C03A5F39B592AB4B401098B8E0A2EB34C06BEBDE019AAB4B407EEE1FF89BEB34C004623AB4ABAB4B404AB4FE378FEB34C08041A323B2AB4B400D7194ED8BEB34C01351878BB2AB4B407E9DEBB88BEB34C0A5A7BEB5BAAB4B40C0B0CE9E87EB34C01B21EFA2BDAB4B4093D782E885EB34C0A2B0C71DC1AB4B40AA0C1D6C83EB34C04143406AC7AB4B406036BF647FEB34C0A2E395B9CFAB4B40920E53AF7AEB34C084EC750BD3AB4B407EA4E6C877EB34C0B2206858E1AB4B40A94466D76DEB34C09901FCEAE1AB4B400D52178A6DEB34C0CAB16C69E9AB4B407B2A2D3C67EB34C0B7CB96F5F0AB4B40AFAE254C60EB34C0F2C165F7F0AB4B40225BC44760EB34C093E4C736F5AB4B405EEBE75556EB34C08116143CF5AB4B40D30F774856EB34C0E9F99641F8AB4B40DEFBC1084DEB34C02675BD4304AC4B40802106A92FEB34C045A88DC00AAC4B4093645CD222EB34C0BAC534BF12AC4B4011EF19BC14EB34C0A302D5661AAC4B40B7865BBC08EB34C035C85CD024AC4B40864190EBFAEA34C0AEE93BBC29AC4B4034D90E73F3EA34C0F798B3612BAC4B401600D14CF1EA34C01AC431902BAC4B4085F61D00F1EA34C03852584F2CAC4B40F93BF5E5EFEA34C0D27380D62CAC4B402ED38907EFEA34C0FBF6B5252DAC4B40F2D92283EEEA34C05B8AA09B2DAC4B40D2C1123EEDEA34C04A40E64436AC4B40137E53C8D8EA34C06AC35C6F40AC4B40FF0E15EFC6EA34C0D0E3574648AC4B40ED6F1313BBEA34C0D8F4B1CB4CAC4B40CD675EB2B4EA34C02E96A5724FAC4B40DA50FEDAAEEA34C005C629CF51AC4B407F187E47A8EA34C0D613962758AC4B404729291C90EA34C09876AA7261AC4B40934AEAFE5AEA34C0DA979FC062AC4B40D868040854EA34C0B07F84096EAC4B401733C98F1BEA34C0B298E95C76AC4B4058D942E4FBE934C032F892EF92AC4B40BECACBF4A5E934C094B4C24397AC4B40DB5540D98FE934C0B12EC50798AC4B40248051128CE934C002471ED29CAC4B40949E933975E934C008EB2EC69DAC4B409B7384D970E934C012012E25A1AC4B40A677ABF261E934C03DA52D01A7AC4B40BC17291E47E934C0B3B834C6B0AC4B405945825725E934C046DDDFBEB8AC4B403A3496BD0FE934C0DC026F79BBAC4B40E7A731B808E934C014502287C1AC4B40E4D625EBF9E834C0BB2267CCD0AC4B404119DA68DCE834C099B2A7B3D5AC4B401752CBE8D4E834C081F52702DDAC4B405603F22FC8E834C0AD9794A7E6AC4B402A998733BBE834C03932E685ECAC4B403151374EB4E834C016E32CACF4AC4B40A2B64EC0ABE834C06C41BC1002AD4B409B6537489FE834C0E14E491E07AD4B4039A1B5EB9AE834C0EA0C320A24AD4B40FF2C69DE83E834C051230A4343AD4B4052A0F68A67E834C0F15E62EA5FAD4B40774993E84AE834C01B26FB6966AD4B4042D2530045E834C089409DC886AD4B4079BAB5642AE834C061B8921690AD4B40B3D247D123E834C0369B69749EAD4B40D12274411BE834C01F3AA8839FAD4B40973317A31AE834C02E567BECA3AD4B40706DAB1D18E834C05FB7400FB3AD4B40819F7BF20AE834C0090CBDFDC3AD4B4061974F92FBE734C01AEE15E1D8AD4B40F1A9BE59E4E734C01483E6A3E9AD4B406E77063FD0E734C0BF0DDA5710AE4B40D81B2C929DE734C0CCF0361B19AE4B40D516CF4891E734C04AE89F0231AE4B402A6493096EE734C0A30E25513DAE4B402454AC2659E734C068CF9C2F49AE4B405A11A6C843E734C083C592E752AE4B400A3293D030E734C081DDF95CB2AE4B4016D1BF2162E634C05DB61337B3AE4B404B3A1A8B5EE634C09221D726BCAE4B4049A893EA40E634C0AADE9EBECFAE4B401A3B416D0CE634C0B6DAFB4BD9AE4B409E2E9DB9F6E534C01A230034FDAE4B40E494AD33B1E534C00083824301AF4B40059EEFCBA9E534C0977D252614AF4B40821A825C89E534C057200AEB19AF4B4099A1533880E534C050B2F60B2EAF4B40C09ED3D362E534C0B40BD8FF37AF4B40A67F191E56E534C021BD6EAD4BAF4B400CA0294B40E534C0EF3D63B85DAF4B4066CB1BD330E534C0A52549CC83AF4B40B729F0E118E534C006296CC38AAF4B40CAE0200E15E534C0C2E530FF9CAF4B4071B51E730CE534C0813EE4C2B7AF4B40800F3CADFEE434C025EBCBE1BBAF4B402D7F4274FCE434C0BBEDCADCBFAF4B405E4D8D94FAE434C03E9CC5E2C5AF4B40D7F37655F2E434C01F964A12CBAF4B40A2FE2348ECE434C0F5EF871CCEAF4B402ECF61E1E8E434C06D72813DD3AF4B40BC393562E3E434C03EC4340AD7AF4B40359F6C20DFE434C0B5BFEFD6DCAF4B4059677EE3D3E434C03B058628E9AF4B407EE78011C3E434C022335F12EEAF4B4063B6A6BBBBE434C067B2BBDFEEAF4B40B1E1728CBAE434C0189B5475F6AF4B4027AF8579AFE434C02F5C6D6605B04B402C8463E69DE434C09F3E3DEA0BB04B40371F72EB8DE434C04C7A8F4713B04B4057B05D9D7EE434C0919CE92E14B04B404B385AC37CE434C0EAC533B421B04B4078B07C7361E434C0E7A67CDA21B04B40A6AA542661E434C0420EAB8C3BB04B40643ECF7B2DE434C0FFA759AC44B04B406317606A1DE434C03056F3D14DB04B40276B7F520FE434C07BDD1CED51B04B40ADDFB85709E434C0C986AE5D56B04B4074AD9C3C03E434C00CAC9B2959B04B4087FFE3EAFEE334C0BCA6E1FA5CB04B4010A8BF52F9E334C0BC60553866B04B404DA3E27BECE334C04709E4216DB04B4002C891C3E2E334C00F955D7476B04B40C7005E34D7E334C04971B1B88DB04B40F66C07EEBDE334C00567FDF994B04B40B0AF57D1B6E334C097A3EE429FB04B40179815C7ADE334C0498BBFCFA0B04B406B827572ACE334C0C8FD027FA6B04B4003E6F3AEA7E334C072524321B2B04B40AD19EA549DE334C0AC306E34BAB04B4047544FFD96E334C0D9C53812C7B04B401B35FF2C8EE334C033299B73CAB04B40A0C8CDFE8BE334C0B4DCB355D9B04B402989A0FB82E334C0FE248EB0DBB04B40FD02D09E81E334C0155D6A69EEB04B402B95B24977E334C0A0E9CF5CF7B04B40F361190172E334C048CA52EFFCB04B40999A720B6EE334C06179260400B14B4041231E9B6BE334C09874E5B909B14B402D575CF063E334C003D935B50FB14B406FB991EB5AE334C00F669ACF16B14B40741593AC51E334C038FF32F336B14B40BB19264524E334C0C86409FC39B14B400368412820E334C09C493BCE4FB14B4045BE7BCC03E334C06D1EDBD052B14B4003F3610B00E334C076B229ED73B14B402DDE2371D8E234C0FDA88CF280B14B40541B45FEC7E234C0847C452E87B14B406A200BE1BEE234C0A782199293B14B407D167089AFE234C0472673CC9BB14B405D8F4502A7E234C005C1633FA4B14B40C2A3FC3C9DE234C0839F3498B0B14B401B123E448DE234C04942445EBBB14B40AEA5412E7CE234C0679DE9A7C9B14B406F72195261E234C0447D4C8ACFB14B4075DC2C2857E234C0BF8FC7D3D1B14B4059B8988753E234C0F471AD9ADFB14B404992DFAD3DE234C0362EC4A003B24B404318BE3FF4E134C0C6CBEB2B09B24B40DCBD4390E6E134C00C9E6EFA0CB24B40FE55A625DCE134C0F39486FD13B24B40DE2D753ECBE134C0D289B98B1AB24B4089C41C45BDE134C0130805781DB24B4036139B61B5E134C0617FA5412AB24B40A77334548AE134C0110B5F6C34B24B4080F43AA36EE134C0ABABB2AB3AB24B40FA9967AA60E134C0AE6B622C44B24B4066C88F494EE134C03DB3155648B24B407B15255247E134C0CA819C7A4EB24B405AE03AE13DE134C064C127A258B24B4054F518912FE134C00AFD3A9967B24B405EE9504E1EE134C05FE50DBC6EB24B407BC675B417E134C0565C1D5178B24B409EBC483D0EE134C03C97F89C87B24B400111E03702E134C05C01F98F95B24B4000995CDFF9E034C0170A4B729CB24B4053555749F6E034C0F26CEA29B9B24B409E11CE80E9E034C01F43F6D9CCB24B40ED13AA35DFE034C044F6A35EDEB24B40ED47D481D5E034C0EC9C93D6E5B24B403EEB4979CFE034C07504AC1EEAB24B407D27CE3ECCE034C0356A460FF9B24B401D69C8C4C1E034C0469F7A89FDB24B404E16F6DDBEE034C0B92A3A9706B34B40A8BB0D79B9E034C04EEBDA171AB34B40FA2BD2FDACE034C0F2385ABA2DB34B40C24615E89FE034C0703E540C33B34B40F07CD1B09CE034C0F93757883EB34B40CB49677296E034C0C315B83C43B34B4058DE4B2294E034C0420993AF5AB34B40A95B5CCF89E034C05CA3F16B5FB34B408AA6C1F687E034C0DE4BC4906AB34B40F9991D2D84E034C00F11DB9273B34B403EF3EDF281E034C0AA807C837BB34B40EF5C48B680E034C083CF60287FB34B40300B064780E034C044FE238C88B34B404BA7E97F7FE034C005C4DE8291B34B408E9BF5567EE034C06C75B94CA6B34B407217716D7BE034C0D8B8D4CBADB34B40E413E99176E034C08534985FB3B34B40517332AC73E034C0F6AA46BCBBB34B4014EE254B70E034C0479BBC94BFB34B40FFC13CED6DE034C07B05715CC7B34B40FDBFB1A367E034C034C1068ACCB34B40396366CA63E034C01D810E2AE8B34B404235BB0151E034C0BCF795C4F0B34B402D62A8034CE034C0CA946B9EFDB34B40E0A912CD45E034C0E632240F02B44B40D2D21BDE43E034C0B1A73DDA1CB44B409E4E5C7939E034C0D61340E124B44B4079F2200837E034C07BC9CFFC3EB44B40C834203E31E034C0FDA8EEBF45B44B4070F74C3430E034C0A50949E84DB44B40D56B2E812FE034C0AC1BC7D751B44B408A5411932EE034C0C9A275AB57B44B406B1A7A332DE034C032DBFB9062B44B40C61DA1AF29E034C02C74050D65B44B40DD9A596D28E034C0D2D3FB776EB44B4007D8124321E034C0033BD7BE89B44B40E616BF8D0CE034C0D3B7A893AAB44B4053302890F2DF34C0620922E1B7B44B40F3718EBDE7DF34C09D2A3DA3BBB44B40888E83DCE4DF34C0EB576011C1B44B401D1875F4E0DF34C0180050B0C4B44B407815A23BDEDF34C0720089BDC6B44B40EBB7489EDCDF34C0E70109D1C8B44B40BDEA02D0DADF34C07C4A7823D0B44B4083451A48D4DF34C04A202950D7B44B408EA8C86CCDDF34C0ED1C7008DCB44B407BAEFB37C9DF34C078ECDF04EDB44B40AFABF525BBDF34C03B3C8451F5B44B40C3903022B5DF34C0B9315181F7B44B407A3E6FC4B3DF34C0934F52F9FAB44B4000781018B1DF34C04C64BB0000B54B4036276387ADDF34C0B33B908B25B54B407036E82E95DF34C0AC8AA0D32BB54B40BC405F9091DF34C05D0F4CF647B54B40AE98255883DF34C094822EE250B54B4047B1B2B17FDF34C0953BCA6A5AB54B409F5826B17CDF34C0AD03B6F95EB54B400163ECB37ADF34C0321E0D1B68B54B40A556049977DF34C00AE6A00B6BB54B4074A943E076DF34C0011356E575B54B408AF926E671DF34C0602545827DB54B40344340066FDF34C0D196B2507EB54B404BE9C5C86EDF34C0DF648C8481B54B40016012CA6CDF34C08720D6E686B54B40FFA53FDA69DF34C0626B413FB0B54B406AA2B96F4ADF34C06FCAE5EAB0B54B402DF2BBEE49DF34C0E3C33030BAB54B40CC46EA0A43DF34C09A3E1069BAB54B4014D2D0CF42DF34C076BE4ED0C5B54B405205D0C338DF34C0ECA535A3D1B54B40DE3E701A30DF34C03AF9DB80DDB54B406F8B5A1029DF34C082F425C2E9B54B402FBA470425DF34C0D381ACFFFDB54B400D511CE420DF34C002F5729D07B64B407C64F7DB1FDF34C0A675348915B64B40E9A39DB31FDF34C057C1D1E71AB64B40DBE283ED1FDF34C070F2AA1D23B64B40B83785B620DF34C01C424D4F2DB64B40C33B8E9821DF34C0B250DB5635B64B40F30F007923DF34C0265C25D939B64B40CCAAD8B523DF34C050ACD3F93FB64B4089073A8A24DF34C0A9C643E841B64B405FB22AC823DF34C0144AA82E4DB64B40AF3422AD20DF34C096C5426755B64B401596715B1FDF34C044CF535457B64B40FDA675C21EDF34C070486E1462B64B40DB86459A1CDF34C086D3C08270B64B40A9DA1A451BDF34C03A5F218179B64B403785B83E1BDF34C09776F35A87B64B40C51E77721CDF34C04A7602118BB64B40E54B2FE81CDF34C0B6E72EC198B64B408CB0C71C1FDF34C078B75F6FA3B64B40079BE5FE21DF34C0A0F7530CB7B64B401A47DB7529DF34C06C851DC7BBB64B409334DB7F2BDF34C004FBC677CAB64B4074B2709532DF34C04B296655CDB64B40ABA6D5C233DF34C0ADDF3EB0D2B64B4048F76A4536DF34C0E93B87BFE5B64B4040A24F5340DF34C0A028C838F4B64B4006F8C02E46DF34C0BA597D82F4B64B4041CACE4C46DF34C06ED92A8BFAB64B40B4F761C748DF34C0A00AE42DFFB64B4027F949EA4ADF34C0E6D1557806B74B40AE3002A54EDF34C0C761A7AC0FB74B406904A16E51DF34C036E553461FB74B40928720C358DF34C0C751D2AE20B74B4097A158AB59DF34C00BF27F0D2BB74B40E29DD6535DDF34C0EBEA493637B74B40989BFAEB5DDF34C0C70074F53AB74B404C28A41C5EDF34C08EA1C51A4DB74B40B1C14A5C62DF34C0FED1026C64B74B402B1F63336CDF34C02F31DE4C70B74B40CDDADDC672DF34C097FB3A5372B74B40768AC12C74DF34C00D32627087B74B40425C70C87ADF34C01A19BF1C8EB74B4045EFDA567DDF34C0D854935292B74B40411C67407FDF34C07D17ABCA92B74B40C93E8F777FDF34C0546FF3BFA0B74B40E8CEE7F285DF34C0FCC6FD72A5B74B40D985F2D687DF34C038F29551ACB74B40073707828ADF34C01A57A770B1B74B40B5EC46C78CDF34C0CF35F232B9B74B4089A366A690DF34C0964AB51FBEB74B407381F46093DF34C0FA1FC885D3B74B400E11DC6EA0DF34C0031AF8B6D9B74B4063E3C375A5DF34C042DAD30CDDB74B4073429707A7DF34C05574B9DDE7B74B40DDE8F161ABDF34C0E7E3E323ECB74B4079B7E07CADDF34C01D74577BEDB74B40709657FBADDF34C096A67363F6B74B40AF1C521DB2DF34C0D3AD54C2F8B74B4070267572B3DF34C0F6F3A016FBB74B40CE35EB61B4DF34C0ED1F95E200B84B402788808EB6DF34C0393F71F708B84B4077E6A7F5B8DF34C0463270CF10B84B4099CCF8CCB9DF34C0B9749D2419B84B4082C6D04FBADF34C00D838A151DB84B40849463B5BADF34C0845D2ED927B84B409A2E7437BCDF34C0D4FF81E229B84B40D7C2A07CBBDF34C02BD138AA2AB84B40F88EB036BBDF34C036982F5330B84B406A421947B9DF34C0B64E67B832B84B403BAD6E60B8DF34C08021462138B84B40E0E4BBD7B4DF34C09C850D6247B84B404505597DADDF34C038F96F8A4CB84B409CA862D9ABDF34C007AA0FAE4EB84B40D0BC5137ABDF34C028A3D25751B84B403B7B947CAADF34C0B6B985DB57B84B40500F06ABA7DF34C0133B81A45FB84B4013D9B377A6DF34C0B19A378E61B84B40441D139CA5DF34C0F6E7CF3E62B84B40470B7F26A5DF34C002F917376BB84B40ACC6EE2DA1DF34C0F939CCDC6CB84B40761023F09FDF34C04ADF113471B84B402E0CF14898DF34C0F1E748DE7EB84B4023FAB47287DF34C07097BBCE84B84B40E9A8DE7081DF34C0BA7274488BB84B407A998A617CDF34C02A1A3BDF97B84B403A28D99B70DF34C0B88513EE9CB84B4098DB09EE6CDF34C01C1828C3A1B84B40E97F17B269DF34C050184AC4A6B84B404495A4A166DF34C03343B654B8B84B40D598D9435FDF34C0C497912BBFB84B407AE32EA95DDF34C0875B008FC7B84B40CA8A3B675CDF34C09A428E61CAB84B4071E39E375CDF34C064FBB9E8CAB84B40D5FE54165CDF34C03EC1B475CFB84B40C466E7BD5BDF34C0C77AE4B9D0B84B403DABCFFC56DF34C0863E0243D7B84B4019B9F2A143DF34C0CB583B27D9B84B402BD4D25D39DF34C01C359DBBE0B84B400146AB541FDF34C0207BD22CE4B84B40F81C5D8C15DF34C075D764BAEEB84B40DD99A0B6FCDE34C0D915CB6FF0B84B40375BC15BF9DE34C06D41B03FF6B84B4098CB2429ECDE34C03514BF5500B94B40B8C03331D8DE34C0A67F1AC30CB94B4097E9848FC3DE34C06271FB8812B94B40581E178EBBDE34C0B71ACDA21FB94B40D2543E52ACDE34C0AFBE560828B94B40E0F6B841A4DE34C071EA909039B94B40CD385A6097DE34C033F7EFD044B94B409B4B937591DE34C00CB74DF147B94B40399947EC8FDE34C0E1636ADD52B94B405DC3E5ED8ADE34C0A8B8686B5EB94B4059D88D0E87DE34C056958F2463B94B401E48AB0986DE34C0D38FA3D567B94B40437908D383DE34C0F9753CCB7AB94B400ED505A57EDE34C0F17B0EE690B94B40BD46E2DD7CDE34C0FCCB487E95B94B4005781BB57CDE34C09D9B0E49ABB94B40C525C7F27CDE34C07461E544B2B94B407EB5FA827DDE34C055C2C0B8BAB94B40C55B9AC87EDE34C0CBA68EF5BAB94B400BE1E5D17EDE34C0C33588EAC0B94B40ACB6A7BE7FDE34C06F0AE1B8CBB94B40A61F7B9C82DE34C041B52BDCCCB94B404D77530A83DE34C0FF998290D3B94B40507DF80C86DE34C030016A2FD5B94B409AD39FE586DE34C0F1C5A4EFDAB94B40FCC457468ADE34C077ABF873E4B94B409D034F7F90DE34C05FBBEC85F1B94B40145CC52E9BDE34C00EDAE003F9B94B40DDFE1E9EA2DE34C0DEC3A9B906BA4B4088750037B3DE34C04E0C06FE15BA4B40CCEBEE6BC9DE34C0D75021501EBA4B40DC5251B9D4DE34C00F9D1D0926BA4B40402A3F1ADFDE34C0682FDC5526BA4B4058DDCF81DFDE34C02E081B162ABA4B403B9CFE96E4DE34C07FE67AC22DBA4B406C553D9FE8DE34C0B53DA9FD2EBA4B401CB242FFE9DE34C03842CA3035BA4B40F898A10AF1DE34C0BC7BD1A13DBA4B40325B72D8FBDE34C014C31A1D45BA4B4067ECDA9306DF34C0F3E14E8B46BA4B40D7E6AFAB08DF34C033159CFC4CBA4B40CA64144912DF34C0762BF12552BA4B40F2650C8A19DF34C0A69FE5095DBA4B400862CC712BDF34C0E7C504BB62BA4B406F027A5B36DF34C0C79B500C6CBA4B4095ACD7334BDF34C0754086EF70BA4B40A25CF90358DF34C051A05BB271BA4B40FAD87ADF59DF34C02B4F4C0375BA4B40B7DC8F2E60DF34C0D64C424C77BA4B40C89822AF64DF34C0ABE9BF497CBA4B407F112EDE6EDF34C0B7C4C7967CBA4B409530207C6FDF34C08F6BBCEE81BA4B40656A517E7ADF34C04F17E0E38BBA4B40585431918EDF34C0F743E8998DBA4B40D3986F1C92DF34C0B872CCB39BBA4B4078F1A31EB0DF34C097B98FCBA1BA4B405E5EB686BEDF34C0CA6AB82DA2BA4B407FDA4189BFDF34C02C077171A3BA4B40E7ADEA8BC2DF34C0AB1F57E5A8BA4B403D75C56FCCDF34C0854ED8A4AFBA4B40C1603F07DADF34C01CDBD7ACB3BA4B4019C6430DE3DF34C0C75B7DC2B9BA4B408744E4B5EEDF34C0D98F0BC6BABA4B400A0FF9AEF0DF34C0618BF8DFC1BA4B402738ABB7FEDF34C060FD36BEC5BA4B40D5A0EB5A04E034C0B99B6B2CCCBA4B409699C14E0DE034C0ABE30680D1BA4B40A63A554C15E034C045107736DBBA4B4009EB90FA24E034C0937D9BC4DFBA4B40F07CF1B82BE034C0CCBEE7C1E5BA4B40B46CAB6035E034C0988ACB84E8BA4B40967927383AE034C07B597428EDBA4B4061F5561141E034C0B97EBA4FF6BA4B40B314CB714DE034C08EEB6F87F9BA4B4029226BFE51E034C0E78AA6DCFCBA4B4060E725EB56E034C0D20D6A4003BB4B407094856F5FE034C05A7E066309BB4B404851B65868E034C014DB510C0BBB4B40BCD590F86AE034C0F306477911BB4B406D9E2AAE72E034C0739C572212BB4B40D653DF7A73E034C0534A98F71CBB4B40EAE77FB780E034C06142B70623BB4B40048F1CC688E034C05F96372825BB4B405E22B3D98BE034C068626AF729BB4B406C299C0191E034C08DF81D3C2ABB4B409EA3363991E034C0D6BD9F452ABB4B403832121991E034C047DAA4EE2ABB4B40B729B4C991E034C06DBE16F82CBB4B4011D1BB6F93E034C0DAD6A62430BB4B4012AF7A6C96E034C04BDCBDBA38BB4B40CCFB00BE9BE034C079E90FF43ABB4B407C1DCB989DE034C052E099F53EBB4B401E28C16DA0E034C088D7DED440BB4B40711DB404A2E034C0CF3320B645BB4B400D0DCE60A5E034C0FDF5864047BB4B400583F28DA6E034C007E41A414ABB4B404C317A65A8E034C0E7A67D6F5DBB4B40E846E2CFB8E034C0B0DBB36062BB4B402FC8445EBEE034C077C115FE63BB4B40F17CDECEBFE034C08FB7B5CC69BB4B405733F274C5E034C0F182452E75BB4B40FC2ECF7BD1E034C0353C8CCD75BB4B400EC29D25D2E034C0EA15976B82BB4B4010D6A4B7DFE034C08624E6E684BB4B4030A15A7BE2E034C0A9F28DC089BB4B405CFE1813E8E034C0E9E228F98EBB4B40D51B3C8DEEE034C02A163BDA92BB4B4070C7F0B7F3E034C0A6A46C579DBB4B402AE330F003E134C0A998D6BBA3BB4B40F853C15D0FE134C0AD279076A4BB4B401B532DAF10E134C0EF2CCFF9ABBB4B4054BA04681EE134C019D8F0DDB2BB4B40138F660729E134C0E561CDD2B6BB4B40544D62442EE134C00D894B31BABB4B40F4A7E5F032E134C01A1D227AC1BB4B40A67CF0853DE134C0F165764CCABB4B4009E7EBE646E134C022BF8BEFCBBB4B40029FF1AE48E134C069E933A2D8BB4B4044763FD256E134C0EA7B5C83E2BB4B40553FAEE460E134C0D556AAAFE2BB4B4009D1F51161E134C0B619D41BE5BB4B40F0365A8D63E134C08A19B3F9EBBB4B404392F85669E134C00B53B0D9EEBB4B40DF6472816BE134C00A2BC201F5BB4B405CC15A1470E134C0C077B5D8F6BB4B4037EDC7BA70E134C021B86CA302BC4B40DF0F130278E134C02D252AD20DBC4B40219D062182E134C0C2C6889717BC4B40183A9B828CE134C042EC29FF17BC4B40DF6757F18CE134C028EDE2EC24BC4B408E854DD69AE134C00B916B7E2FBC4B402F251DC5A4E134C0EB01ED483CBC4B40ECC5ACEAAEE134C067A3BE9C3EBC4B40330129D5B0E134C045CF07FC46BC4B4022D47AFAB7E134C0B876C87848BC4B40A1B57611B9E134C022F158454DBC4B4071819CB9BBE134C08AAF0D9B4DBC4B40B14E75DDBBE134C0A2F72F2F53BC4B4036F341C4BAE134C0B863B79B54BC4B4088ADB881BAE134C0DEADB2475CBC4B40A8576E37B9E134C01EFF2E325EBC4B40A8CE33DCB8E134C05B51C83564BC4B408A8DD208B6E134C02E21883965BC4B40370E48B1B5E134C0410D0F9E65BC4B40CB6C2F80B5E134C03B34F58877BC4B40FD7F1222B0E134C017414FC77ABC4B40230C5BC1AFE134C04141AB477EBC4B40F1AC9012AFE134C06C8A46188BBC4B40887BC737AEE134C003A06EDA97BC4B40FC4BEEA1B0E134C09508017D9CBC4B401722C91CB2E134C008AF6F69A8BC4B4039304A6FB7E134C0EF9D5FDEB3BC4B402DB2EF9CBFE134C063FB2A85BABC4B4035A77B50C5E134C0369540C2C1BC4B40F4627B3DCCE134C01FC17D1FC4BC4B404C5D91BFCEE134C023741728C5BC4B4050344EA2CFE134C076589D2DC7BC4B409624DBC7D0E134C09AB3921ACDBC4B401D60496FD3E134C04A9AA3B5D0BC4B404E650931D5E134C0058C0BA4D6BC4B40416C3651D8E134C0EEEA3742D8BC4B4057AD99FDD8E134C090B4D3E1E1BC4B40A1674F9CDBE134C0AB5F5E28E7BC4B4091904D9EDCE134C02443948BEABC4B402025A611DDE134C0D0ABA2CFF3BC4B40299A802BDFE134C0FDF88FB2FCBC4B406D8E2207E2E134C08BEDAD2602BD4B400B581618E4E134C0239C3B6203BD4B401FE5EDA1E4E134C00F772F3808BD4B400CD0BDA3E5E134C06C366E9209BD4B4096D799F0E5E134C0F42AC78511BD4B407E9362D0E7E134C0A907C9F915BD4B40A77FD18EE7E134C02FCD02FD1DBD4B40B9EA0192E7E134C0244F57CE21BD4B40B569A7B8E7E134C0C870D5C424BD4B40AE637CF3E7E134C0C11F45E025BD4B4012148FADE7E134C0B0DCA3AD2BBD4B40D425A193E4E134C013AC370E33BD4B4069FEE8E9E1E134C0A2583E2237BD4B4068A5689DE0E134C007AFFC003ABD4B40998F65D2DFE134C0CAE6E3D33FBD4B406AC57C60DDE134C0A09E140A43BD4B4059919DC2DBE134C014A7D31947BD4B40B335AA39D9E134C02904278156BD4B408D0B183ED2E134C0869783485ABD4B4088A24026D1E134C011FBD3DF66BD4B4025E2401DCFE134C070E9A86F6DBD4B40E94EB4E1CEE134C049B79BC972BD4B40F0CE13FACEE134C095F9BB5779BD4B406A655371CFE134C0D048718D84BD4B40C5598080D1E134C0658FC2CA8ABD4B401A2DF95BD3E134C0E483E57495BD4B407933D2BBD7E134C0D4A19AF29ABD4B40BDF69D9EDAE134C0BCC612A6A3BD4B4095F4A10FE0E134C06E669065A8BD4B403D551585E3E134C0932B3327ABBD4B40D668489FE5E134C0D58D5FA8B3BD4B4023D75B67ECE134C0E7DABC4AB4BD4B40260725EAECE134C0EAB449D7B9BD4B402042F76DF1E134C0312EA1FAC3BD4B40F5CCED13FBE134C0B487FE85C8BD4B40AA24CC1100E234C020F4D07CD1BD4B40706CC55802E234C04B2D2A7AD7BD4B40690EA86C04E234C0E457E33CDABD4B40B53E537605E234C0DF708925EFBD4B408FC4D0EE0DE234C05534A8D5EFBD4B40A25428310EE234C0137CE123F2BD4B4044F56B730EE234C09730A989F2BD4B4051D3407F0EE234C07EC119B5F8BD4B409F28233D0FE234C06EC3C79901BE4B40DA103DB00FE234C0EA1A7A7305BE4B409B16EB0710E234C0413ACE4A14BE4B40880E08EC11E234C0AC547EB816BE4B403D19654A12E234C0044B5BB726BE4B40109AEA1B15E234C0C9F9B8BD2FBE4B401B9DCFA516E234C090D80D4B31BE4B409A11D9AC16E234C04372C79C35BE4B40422DFCEF16E234C025F6331139BE4B40F124C74B17E234C0B93F71D642BE4B407279D10717E234C0934BE51745BE4B40B9AE150517E234C07A08C31548BE4B407F7CA41217E234C066BDE9EE48BE4B40A92725F016E234C075C2CC3C56BE4B406A851E7A17E234C0DAF6101260BE4B40CAD3DBC316E234C0ACAB2A756ABE4B405C80771D18E234C0CDBE59D06EBE4B405BB652B018E234C076E123807ABE4B40CB6A74E618E234C0628BB38B7CBE4B40DAF494FA18E234C00801DA3283BE4B4028F8BA5E19E234C05C68C1778FBE4B402A19244319E234C0C961985299BE4B40CEB9D1CE17E234C05B917F559BBE4B405550758317E234C0A047038A9EBE4B400CDE347016E234C0219A04FFA3BE4B402C3A49EA14E234C0CE0F653DAABE4B40C987738513E234C051B8451EB3BE4B404082EC2C11E234C0AF4A82FFB6BE4B4039947DBF0FE234C02832FC8EBDBE4B404B63CC420DE234C0AF9A30AAC5BE4B409796783309E234C0CDA21455CEBE4B406987AB4E04E234C077655107D9BE4B40C30C5057FDE134C008992FBDECBE4B4081E707BCEEE134C00E131EC9F1BE4B40CFB151D3EAE134C0F1FC54A9F6BE4B40EF7F6757E7E134C02A71DED5FCBE4B4004D0F04AE3E134C068BD5EEF07BF4B408F073470DBE134C081ABE41B15BF4B40381CFFCCD1E134C00CA9063C17BF4B40A144034DD0E134C084FE11E720BF4B40ACECE7B9C9E134C050DAE6A936BF4B40ABB0D695BAE134C0485A54C142BF4B4030F99020B2E134C0147E489549BF4B40EA4544E6ADE134C0EF5D79F85ABF4B40A2C8DB7EA4E134C0C734200D68BF4B4082F0E8FC9BE134C05BABD7AD6DBF4B4011FF14F197E134C049CB6F2071BF4B40D196239B95E134C06B83247272BF4B40BBF729C494E134C0C1778F6F73BF4B4086B4CCC693E134C050E278A278BF4B406DA314F58EE134C020C6D26A7DBF4B402328A2DC8AE134C0322632B981BF4B40A29FF06987E134C064BF7E7186BF4B40CE2B37E483E134C0067DDAF790BF4B40FF021F5A7DE134C08037D1CC96BF4B40495EEB6D7AE134C0ED2592FC9BBF4B4063E15E1F78E134C06615B66CA3BF4B4003D3633A75E134C0D9196975A4BF4B406BD046D674E134C0C1372BDBB0BF4B403291824870E134C02B31B89FB1BF4B401266920270E134C0C70F1494BFBF4B4074B5E19066E134C037845157C6BF4B401BDB6B4F63E134C046C0D501C8BF4B409C38D38962E134C0939227ADD5BF4B40D07ADE735CE134C0BE09930CD7BF4B404191A9C95BE134C0599213B9DDBF4B40B4A282FD56E134C09DBBA9D8E4BF4B4013E8B77852E134C067A750DBE5BF4B40BA30EBE951E134C0DF206E45E7BF4B40D0A8DF7850E134C0595AC2DDEBBF4B409D8A6FB74CE134C08C0EB26BF8BF4B40677CE56044E134C047387024FBBF4B402340ADF742E134C052709025FFBF4B404D34EF7040E134C02B43DFB707C04B40E197D0DD3BE134C0E75ECA4E0CC04B4075BAE9D939E134C02676DFCF21C04B409A22EC3135E134C01EA7453D25C04B4048531B3235E134C0D6D77CD82FC04B4019F7944031E134C001E1A73E34C04B40D17AC02930E134C09D2822F137C04B40AEEDF4622FE134C02702AAE33CC04B40E95C4B882EE134C0BC0D30C741C04B403A78CE932DE134C0A2B30C674DC04B40EC4E03A92CE134C010CED84759C04B406D655F192DE134C0D77C753D5CC04B40D9FCB24B2DE134C074B2C29969C04B40F872D5932EE134C0982699666BC04B40444156C82EE134C080ECC12476C04B404FE6333330E134C01FF342E878C04B40618F741230E134C05463163784C04B40B04C92D230E134C0F1F3933285C04B40D078B6FF30E134C053858A7289C04B40E6CA851331E134C015CD6B358DC04B40C86D1C4931E134C06AC84D8291C04B40ACFFA6AF31E134C037A77A1F96C04B40FD98227F31E134C0BFBB5EBEA1C04B4094026D5D32E134C05780EA82B6C04B40E0FD575536E134C0CA61B6C0C5C04B40FE1FF1A53BE134C0DB4B6DA7D7C04B408330BCCC44E134C06F5874CEDEC04B4013BF310949E134C0932F7E98E9C04B4091F6955250E134C0EF66DDF6EBC04B4058EE6AFD51E134C072943354F5C04B404E310CDA58E134C06F7063CBF7C04B409890BF7E5AE134C0FFAA6BB5FBC04B40E06E05705CE134C0A5B95E2501C14B407096650E5FE134C00B9CF69A07C14B407317C83261E134C00B1924310DC14B40227F416163E134C0CDD9F9EE17C14B40422EF23668E134C035EB04ED1AC14B402793BAA869E134C0990FBA9023C14B4090661E1D6EE134C029C6FD982DC14B4052FD124B73E134C0CD59146830C14B4021E9E56C74E134C008417E0035C14B403A8F398176E134C01CC66B5239C14B403D9803AD78E134C08023BCEE3FC14B40E80B887D7CE134C0486E66FB4BC14B40664F6E5C84E134C0C06BEBDF4DC14B403547D2A385E134C022B3CB1256C14B403133A15D8BE134C03366199056C14B405A7B238B8BE134C0998639E964C14B402244E4FF92E134C05429D7326BC14B4046D46D4B97E134C08F32300274C14B408269C4479EE134C0F4D533A078C14B40E70C8378A2E134C0471A68AF78C14B4089B05682A2E134C05A64499180C14B403539E85BA8E134C00F0C91AF86C14B4051D6D581ADE134C07876887F87C14B40FFC82F1FAEE134C0C807EBF98CC14B40FAB7222AB1E134C0769255C29AC14B40D2CA6FA9BDE134C0CC41B1389FC14B40882D0E95C2E134C05D9FA6D0A5C14B4029B693C1C6E134C0EFDF4B3DB2C14B406C9C8AB2D2E134C0A344B396BAC14B40090BD93EDCE134C0AFAF7FF7C1C14B40F03D7CACE4E134C0FFF45DF7C9C14B40AB57D4A1EDE134C05678AC8CCCC14B4015A97436F0E134C0AA7CC771CFC14B40A19E623AF3E134C0EECE60ACD4C14B406121D4E6F8E134C0C6E44D1ADAC14B40DC1A4E43FFE134C044635A1BE5C14B40D6B441260DE234C0C120B45DEAC14B40C51D045813E234C0B972A213ECC14B4025AF557E15E234C0CA21787BF5C14B4077E465AE1EE234C040A929D8FAC14B40D29FE6AC24E234C0C0113C3202C24B40B1682A1F2CE234C076A806DD05C24B405A29780930E234C0F66DA12B0AC24B406FA8A4E034E234C0975E6A820CC24B405CF7049837E234C0BCAD8A4211C24B40A188B14A3DE234C0F10C942018C24B406FE6B23A45E234C0C0A33D981EC24B409E993CDF4BE234C00C2C3EE525C24B40BB96743353E234C0BA6CB51333C24B40738E98EC5FE234C0816C72AA37C24B40E9ADEDA864E234C0D8CA4E123BC24B40EAECB46868E234C010059BAE3DC24B408A957B286BE234C0C7A8C6BD3EC24B40DA081C176CE234C0460FC54D48C24B402D13080873E234C06E6E529556C24B40F7A15B617DE234C098E0AC3C5BC24B406A96210681E234C048902ECE61C24B40C656C28E86E234C0F6D2658A63C24B40EB07788787E234C093E9751C66C24B40B22A300B89E234C0BD5D61706EC24B4097084C328EE234C00BC76C686EC24B409072B85F8EE234C0510AD44679C24B409A2007A894E234C0356A598E7CC24B40E57D4D4397E234C0FF0233BC84C24B403847FD809BE234C0DE67E8628FC24B40FD5758E4A3E234C01D07CE7390C24B406363965DA4E234C0A85A078496C24B401B038076A7E234C084766011A9C24B4074545F32B2E234C01EFCEE7BB0C24B40BF1BA7C3B4E234C04CDACEF4BDC24B4058146668B9E234C044CBE688BEC24B40FCD3569CB9E234C0F1EB84FCC7C24B4025C49FFBBCE234C04BAE8E37C8C24B409EAFD410BDE234C099436C81E2C24B40B0ED7B92C6E234C046BFD500E7C24B409477CA69C8E234C09EE94F1202C34B408B425BCCD4E234C0316FD86C05C34B4000A0AE74D6E234C083DD185D16C34B40D4438874DFE234C0E2C2370725C34B405806CA93E6E234C04A5999CA2FC34B40D178E3A3EAE234C0B7EC828A30C34B40A019DAEDEAE234C006AD37373CC34B406CEC6B85EFE234C0D3A5D22448C34B40F43F9B9DF3E234C092BEF7E38AC34B407086399F06E334C08E46BD708EC34B402AD063C307E334C0E59A587492C34B402E54C63309E334C06B565ED3A7C34B405F847FF40FE334C006BA246DAFC34B40347616F712E334C006B9C304C0C34B40598B45E31AE334C0EB6CC9D7C3C34B40FCC218E01CE334C0DA371DFFCAC34B40C6EE26E620E334C0D04A22ABDAC34B4008270CBD27E334C0A66AE94AE5C34B4018B33F912BE334C09C222DF0E8C34B4099E32BE22CE334C0935E6206ECC34B401D6F3C192EE334C0EFC5D97A13C44B403D121CED3EE334C0FD23070E2AC44B40DC62216947E334C0083571CF2BC44B402C85631A48E334C0612BE3F051C44B40D1049EDA57E334C00D7EF67253C44B4020D9548058E334C0D8CF9A3F60C44B402EA7E3335EE334C0E6B01CB470C44B4062C2ED0065E334C00AFE770F82C44B40BB1B58FB6AE334C039A99ABB84C44B40D17430FA6BE334C04A81B42786C44B406A481A8C6CE334C07848EB3EB3C44B40F708F4167EE334C0BFA3F473B7C44B4078CE6FEA7FE334C08FE9C2D2C1C44B40DD907EE384E334C07698B382CDC44B40025803978CE334C08B4A9DB1CEC44B404DB23A568CE334C0D30F91A5DFC44B409DD08F778EE334C01C3C671FF0C44B4080BFDF4496E334C0CF5F3343F3C44B40FBDFE95598E334C0C8D22CD3FFC44B4007B07194A2E334C06D1AB5770BC54B40F9031B21B0E334C0348133F215C54B40062C0DB3C0E334C0DA77510A1FC54B4042E331F1D3E334C02C20D29424C54B402458F288E1E334C0DC5DC1472AC54B40DA46DB2DF1E334C090FCA58534C54B403F2E5CCA10E434C07E7BAEB23AC54B407DCF0C5527E434C0CD0B44093EC54B4071782F0136E434C0FE07BDF147C54B40F7AD34913DE434C0514E225551C54B40094A909444E434C08A82BC5A5EC54B4004B082314DE434C09D689DD25FC54B40D16BB4304EE434C019311EB561C54B40F68BC8804FE434C0C401ACA562C54B40B44239F84FE434C01ED9354A6CC54B403D53425C54E434C08EDEF21E72C54B4007C1170257E434C0FEE1A3237BC54B40BA8AD1135AE434C020D3856781C54B40DBCA1D3659E434C083EFF8838EC54B40DE0BFE1C59E434C0E6DB02BD9CC54B40199882DD5AE434C0B53706539DC54B40BD83DEF05AE434C09152D037A7C54B408D4296465CE434C0CE345E7EADC54B40691AF9825EE434C05FFA36D2AEC54B40A403B4A15EE434C07CFED169B8C54B40A066232861E434C0D2ED3AA0BBC54B40388A114961E434C067F10853C8C54B40B3881C6963E434C0EEAB2654CDC54B40CD6898E464E434C0C3B1FA77D2C54B400418CFB066E434C0B2D17CE7DBC54B400A284C806AE434C0825DA446E0C54B40BA6CD8ED6BE434C0F166559AF2C54B406EE1369275E434C0E28C3649FCC54B406FF9B9B57CE434C0B323F0E404C64B406238A26684E434C02B81582105C64B409ECA738884E434C00DA5689606C64B4047D8217E84E434C0A7DE8EB308C64B40126DCA6584E434C01BA23EBE09C64B40F0C17BD083E434C05A34C6941FC64B40B815A1AF7CE434C08FBFAD4D29C64B401240DCB07BE434C07791C6F936C64B4007C857277CE434C040B52F8C41C64B40CC2692F47DE434C03B93F74B4EC64B4084DCF1CA81E434C059A01CD55AC64B40DC5A303D87E434C02C98618E5EC64B401894E30189E434C0F477CB4564C64B407C74A2F48BE434C09F068A2265C64B4038F79FB78AE434C06C4E167D69C64B407DF2694282E434C0102A1CC96FC64B400BC5121977E434C02E493B5379C64B40133F79AD67E434C08D6586A381C64B40064414AE5BE434C005CE4B0B8AC64B40795946E350E434C025D7805890C64B40D49A847649E434C0B6A6DACC99C64B40A8CE4B463FE434C003FCA34AA1C64B4045F41B0438E434C0788AE592A6C64B40E536417233E434C01411DDFCACC64B406B2D0F6D2EE434C0DCDACCFAB0C64B4014BED59E2BE434C069A7392CB8C64B40BAFA038921E434C075AABDCDBAC64B4003187E701EE434C09045B649BEC64B4095F243B917E434C04004AA80C3C64B40B534ED680EE434C0535F81F3C7C64B40FE4B990C07E434C0BA158D98CBC64B401059F82A00E434C0019E70E6CFC64B40B2FC0F9FF8E334C085CB38F5DAC64B403E8F15F7E7E334C0E30D6D37E7C64B40383FB09ADAE334C0A8909F8AEAC64B408BC2FB94D7E334C0A0635A66F8C64B40DB3BE46DCDE334C07076A52900C74B4047E9BF41CAE334C0DBF1F6C603C74B40D07408EDC6E334C03C7853CC0AC74B40C26AD99CBFE334C0719582DE0CC74B40ED322BB3BDE334C0301ECE2D0FC74B40FD9ED41EBBE334C05FBAA1E112C74B40D175D831B7E334C0E0FFAB9118C74B40CCAB257AB1E334C0CCE7162323C74B40C6CEA160A8E334C0D0797CF625C74B4086165654A6E334C0498ABD442CC74B40D350D43BA2E334C0C53348A72FC74B4060089C48A0E334C0C1714C9833C74B4007D15FA59DE334C03299D9F839C74B409C65CBD999E334C0A9F098263FC74B40772AD32497E334C0E171696844C74B405404DFB294E334C063164F8048C74B400DFAFA0693E334C09DF19B0254C74B40940823B58FE334C017AC26A65FC74B40600AD1128FE334C0FE7C856067C74B406DE2598C8FE334C00B2043A667C74B404DB92C7B8FE334C01B70AFF96BC74B40034385A58EE334C0208124EE6EC74B408FC1A9828DE334C034E53D5584C74B4069F29AF789E334C013D982828BC74B406EA5B0518AE334C08D0AE8E49DC74B4032FAAEA28EE334C0753265B9A6C74B40B6EAC46392E334C0B06C4933B8C74B40D789B8469DE334C05481E58ABEC74B4063C1C290A2E334C05C942719C8C74B4054CBCE49AAE334C012928DD8CDC74B4022DB87BCAEE334C0A08C32E2D0C74B4096BB84B1B0E334C08DE32ABCD2C74B4099210912B2E334C011A8C36DDCC74B40F5987D37B7E334C0425573C2DEC74B40986C2550B9E334C05A8D280FE0C74B40661BBF84B8E334C0A51F16A1ECC74B4026DF2297B2E334C0665386D4F2C74B40F6CFC780B0E334C06BCD0E8BF4C74B40A9CEC0CDAFE334C05ACBE821F9C74B408DD058AFADE334C05D858DF706C84B40FC353150A9E334C070B2A7CA09C84B406D9C6ED2A8E334C03B41ECE30EC84B40F7212D32A8E334C09DBF853D17C84B401A7EA998A7E334C0F40B540226C84B401373C1B5A8E334C0AF7EE92137C84B405985BA89ACE334C055EEC50B38C84B40D8632EC0ACE334C0A52D34E63BC84B40CC9009AFADE334C01A81E5F43FC84B40551B1C7CADE334C0446515D550C84B4096C08980AFE334C03089D3725BC84B406420CD94B2E334C065D30AE566C84B409C874F48B7E334C087F67EF171C84B405FA41E9CBEE334C0EDD774267BC84B40F9E696F3C5E334C03A5B7A2282C84B4006D4862ACCE334C07C3383C386C84B40CE48B8BAD0E334C0C5C37D3389C84B4037648A4FD1E334C033198EC997C84B40A47EC603D7E334C02D419A39A1C84B4085547F31DCE334C003346849B2C84B4048C3770EE9E334C0521E4177BAC84B40DC6FC909F1E334C096967C19BBC84B40ABF39D9AF1E334C0AFA82102BDC84B4088942035F0E334C0D891D944BFC84B4072B64717EFE334C0308C69E4CAC84B40B8C5F5CAEAE334C0F2543841CFC84B405B1E3EB5E9E334C031B18C63DBC84B4051A276F7E3E334C04A2E8EF9E1C84B40ABF1E511E2E334C08B7ECCDAE9C84B40B24ED16EE0E334C002FFF9E2EEC84B40CF7C9EC9DFE334C09B14956DF3C84B4097E66318DFE334C03AB1BFF202C94B402DBBFC21DFE334C0984B7B2009C94B406306CD1AE0E334C003DED44010C94B40159AF216E1E334C0F98DA2FA22C94B40EB646148E7E334C0EF859AE725C94B40B99A7ED5E8E334C079DEEA8E2DC94B40FE6D34B3E9E334C014D6E42030C94B40BED3F66EEAE334C0BE6EAF5631C94B40E54F086DEAE334C068AE46D736C94B40366EE435EBE334C0C48E9DE237C94B40410BD15EEBE334C06D6071473CC94B40A7DDCE16ECE334C098AD889C4CC94B40BE7DA381F1E334C07AF3E62E53C94B40E74A5C05F6E334C0224E264354C94B405EC3C979F6E334C06071F7CE54C94B40ECB788B5F6E334C077204EF459C94B40C68E2CF0F8E334C06F97B0B65CC94B40DA274337FAE334C07B53270564C94B40A7FBA7D1FDE334C0D32D76336DC94B40DA74EB4C03E434C0C7C53BBD70C94B407BF7F1CA05E434C0B8DBD0247BC94B409782D87F0EE434C03151C5467BC94B40E5ED00A10EE434C09B7A79EC80C94B408BC829FB11E434C0B95DD03A86C94B40A03D9D0516E434C0F0CECF8F88C94B4069C9E09D17E434C05F17E8A78AC94B40DF90101A19E434C0DBD61DBC8FC94B4088305BD51CE434C0193F00859AC94B409207B89227E434C01EFC86959BC94B402B2A2D6B28E434C0AD44621BA5C94B403C1D693231E434C0F6DCB966A7C94B40A586FC9E33E434C07CFF9FA6B0C94B40C78589C53EE434C0B42945E0B4C94B402D17158B44E434C04DFC377AB5C94B402BB8205F45E434C006366E21BCC94B40EDF78E9C4EE434C04F3FD9CCC0C94B407202BA8855E434C0C51E9C02C8C94B40EFF7AFEE60E434C0A4EB334FCBC94B40DC26426666E434C003923F26CEC94B405087C3546BE434C07F6168EFD1C94B409D855B8070E434C0EC0AB337D5C94B40C73E79C173E434C07CD10BC1D8C94B40015A5A4578E434C044C855D7D9C94B40AE9FDF4479E434C02E550040DDC94B404B9D377B7CE434C06A94EBACDFC94B407C919BD97EE434C0069C02B9E2C94B40A8B994EE81E434C035CF047DE3C94B40C09A04B782E434C079869239E7C94B40D298F49386E434C020A383F8EBC94B40181926D48BE434C0B037D314EFC94B4025DCF2808FE434C0E79073FCF2C94B407FB6E41795E434C034BE2C1FF4C94B40E4C9147996E434C0F9CE7047F7C94B403426579A9AE434C028DE2376FCC94B40193793FC9FE434C05F5DC1BA00CA4B40C958E7B2A4E434C026AD97F903CA4B4006961980A8E434C0894863B807CA4B404385661FADE434C09FCA8EB809CA4B404A03AFB8AFE434C0B37FB7800ACA4B40422BA28CB0E434C080BC090612CA4B408B86F7ADB9E434C058EEEB1514CA4B40369704C0BBE434C08B7576F715CA4B4085FA7CF6BDE434C0F83BB48917CA4B4056DE379CBFE434C084FEFD271BCA4B401092C750C3E434C0CDBFF8911ECA4B405460A0FCC6E434C0CE3FF5E520CA4B402234DA9CC9E434C03BEDE17C26CA4B40761C78A0D0E434C04B51C6CE2BCA4B402F753B09D6E434C0F4F5511A2ECA4B400F8D0EB6D8E434C0EA9741FD2FCA4B402DC18CA4DAE434C08FD3F8AB32CA4B409C498863DDE434C04346701134CA4B409C2EDDD8DEE434C0322170CB36CA4B40F3326CC0E1E434C074852B2739CA4B4040BFCC04E4E434C038D671F83ACA4B404D8320D0E5E434C08EFD91613BCA4B404ABCA93AE6E434C0FC88DC753CCA4B40D3787C30E7E434C00F28F50640CA4B40C038F523EAE434C047C6737A46CA4B401AB6003AF0E434C007E16BD94BCA4B400131CB3DF4E434C01753EF094ECA4B40F6799914F6E434C0D287EB9E57CA4B4037008A5BFDE434C04310C40362CA4B406959CBBB01E534C079704A3E64CA4B4054A51CFE02E534C021C900D568CA4B40E95F6A8904E534C0793664FE6CCA4B40ECE36B6F06E534C0727EE30F70CA4B4093C415F007E534C09DFAE9F874CA4B40125D2D820AE534C09955BC7B7ACA4B4097AAA3EB0CE534C0BB2F00597ECA4B4083596F140FE534C0C36A7DD97FCA4B400E960ABF0FE534C08106D9A181CA4B4077C29CB210E534C0DF8147DB86CA4B40C0595D1312E534C02609FB1D8BCA4B40CCC717B713E534C069AF50D28DCA4B40472472D514E534C0AF9A823F91CA4B40A0F4AA5916E534C06B00BC9398CA4B408758D0B017E534C0CE5A24B699CA4B40105441EC17E534C06C17E74C9BCA4B40991C10A717E534C08DEF6C0BAACA4B407D3EDF7D19E534C023B12202ACCA4B4017E863071AE534C00A395F99B2CA4B402B659AF11AE534C0A71893C0B2CA4B40DA6652F71AE534C099B0614FBDCA4B4003A83B9819E534C00DD516EBC0CA4B403D6954C017E534C09F7622C4C3CA4B4056088A9916E534C02FE1FB0CC5CA4B40B18A3EDE15E534C045A79395C9CA4B40BEE0660313E534C0A4100B6DCACA4B400801D47D12E534C0FE142BECCFCA4B40CB5F3D230FE534C08D01A549D3CA4B40B3817A090DE534C0444A7318D9CA4B40F955D3CB09E534C0FA2A1E7DDBCA4B4002EAC19D08E534C0BEFBDA58E2CA4B40222D10BE05E534C060CA4DB4E6CA4B402C8DDD3B04E534C03069C2D5E9CA4B40AC8A907B03E534C0D86A87E3EBCA4B40437EC26A01E534C0155CF708EFCA4B402E3BB6DBFEE434C08384D291F6CA4B4039F7916FF9E434C0BBBDA83BF9CA4B407A19A5C2F7E434C0417CD3A9FDCA4B40420C8EDBF2E434C0680D677408CB4B40B6554219EAE434C0C77E524B0ACB4B403417C1DAE8E434C081198CCE10CB4B40789C2490E2E434C0F26B904413CB4B40CEDE6CF0DFE434C0FCA5791A17CB4B4069EAF922DCE434C0F67B600F18CB4B40C21F8833DBE434C0C0FF06AA1BCB4B406C87F9B9D7E434C0120DBF7426CB4B406DD816DFCEE434C07B09F8D72ACB4B40AE5132DECBE434C0627A407F32CB4B40EBCE10B2C4E434C02E6F2C9C34CB4B40BC9A9A7AC2E434C0ABE2B6DD40CB4B40A1E03E8CB9E434C00EEB1BE740CB4B40B64AC786B9E434C02E31548B43CB4B40E8720838B7E434C0D995A63953CB4B40238A6548AFE434C02706713958CB4B40B721239AADE434C0F016AAB95ACB4B40B4D30D9CACE434C0B01B3C7268CB4B403C44E51CA9E434C0D391B24A76CB4B40C6F6196CA9E434C071B980697ACB4B406BE17415AAE434C0881D20CC80CB4B4092107780AAE434C047C23F7281CB4B402FF2C141AAE434C01BC07BAA84CB4B408705B87FA9E434C0E8A2EE1E8DCB4B40651EC03AA8E434C0B49111F08FCB4B403776890BA8E434C0FAAC880E94CB4B40B6E4B9F1A7E434C0AC4A9A7F94CB4B40124A97F3A7E434C08DB2750B9FCB4B4024786170A6E434C03BEB4C10A7CB4B4099DC53EEA5E434C0032BF9F6ADCB4B40DE74730BA6E434C01F63081EB8CB4B408A886013A8E434C072223E8FBCCB4B403C473E77A8E434C0F6A30895BECB4B400E14DD88A8E434C07FB37A4DC5CB4B40ED35BE36A9E434C0E9A386AECACB4B40E425951EAAE434C0B75C4C14CBCB4B400FD01F30AAE434C0B4237CE4D0CB4B4017939636ABE434C0B103EA4ADBCB4B40C817F826AEE434C0649F6A2FDFCB4B4091AA37ACAFE434C0141EE189ECCB4B40BB9710D9B6E434C072A5F4F1EECB4B40A0876182B8E434C085E38209F0CB4B40FF9A1720B9E434C014EA8CCBF2CB4B400108F62BBBE434C0ED34BA56F3CB4B40D389BC66BBE434C0F61D152CFCCB4B40669DDCFCBFE434C00A864BCD00CC4B40791B37DAC2E434C030D5AE1108CC4B4093BCD3FAC7E434C0DBD375D70BCC4B40DAAAB3FACAE434C0F25AE37D0DCC4B4019BE7414CCE434C005BDECDF19CC4B40BC676A44D6E434C081A6B3331BCC4B40548C07D1D7E434C004E009AB20CC4B407C501D79DCE434C04BE99F2D21CC4B40C41F3DE9DCE434C065B2D75727CC4B40C6F8FB3EE2E434C0C8E57CA931CC4B40CF16C6ADECE434C070D939EA34CC4B4065186279F0E434C055975EA935CC4B405DC8963BF1E434C0E76D4D8F37CC4B407BFD0CF2F2E434C0B93CD65F3CCC4B40A8B30E9DF7E434C00480654942CC4B4079335DC1FDE434C0CE5A5F7E44CC4B40C0730B1F00E534C01BC5CAB146CC4B40F727128E02E534C06F9E259B49CC4B40AEF3F95105E534C0AF357A8A49CC4B408997F98F05E534C0EBEE94DE4ECC4B40F8C78D210BE534C0D86600E04ECC4B400CE81C1C0BE534C04E2C917F53CC4B405BCC165F0FE534C0B1DD61AA53CC4B40763EA7860FE534C065E7C58B56CC4B40F649C03112E534C078F3E4175BCC4B4038C1CB4B16E534C09A9705756CCC4B40A1592C912AE534C035F963306DCC4B4011A2DAA42BE534C0A3FB0FFF6DCC4B408A664B812CE534C0241AB1426ECC4B40F486B5DE2CE534C06B4E36326FCC4B40F62D1E002EE534C073D0BCBC7ACC4B40C55D346C3EE534C0A6D087537DCC4B40A60B26BB42E534C054D4BD747DCC4B4081DC4DE442E534C01B62A02982CC4B409240B7E648E534C0F17551A184CC4B4046C143294CE534C046C15B1289CC4B40C00FFC3A52E534C0ED0093018BCC4B40F373B09B54E534C0889B665A8ECC4B40FE40E94F58E534C04EDBDF8991CC4B4081B515D35CE534C0025A6FD994CC4B40002FB3BD5DE534C0B4B8503AA2CC4B40FB8DBC5C65E534C0EB944AC1A3CC4B400CAA567866E534C0C559AC71ADCC4B401657758368E534C0D0DDE009B0CC4B400F46246D69E534C05FBDD3FABECC4B404B0D521C71E534C052B3B0FBC0CC4B40DEB6407B72E534C0A83E33A2C6CC4B40D7F93E9577E534C03B2D5C6FD1CC4B403CE22BEC7CE534C03C710BBFD4CC4B40022DBA6F7FE534C098887162D8CC4B40104730B381E534C00B067B0BDDCC4B40DE7334CD84E534C0E633FF13E3CC4B40726BA54289E534C008661777E4CC4B40463EE1638AE534C057CFEDC4E6CC4B40405F07B78CE534C000509D64E7CC4B4063DCE81D8DE534C0DE6ABF20ECCC4B403314ABFB90E534C0C9B27DA1ECCC4B40A96EFD4391E534C05038A4CDEDCC4B40801F98F091E534C01DF194D7EFCC4B40F0D2B62393E534C078786AF8FACC4B40E6880B2B9BE534C0BFB8ED60FFCC4B40EF46D5F69EE534C032A134D603CD4B40E15D68EAA3E534C0A3C33E4906CD4B407BFB97B4A5E534C00B8479C915CD4B408EB07330B8E534C00C6900D516CD4B40DFFBD2B8B9E534C0BFB23B0F19CD4B4029DBBE8BBDE534C0C1C3D19E1ACD4B40ADC61DBEBFE534C0DFF584EA20CD4B40E119DC6CC9E534C09F77213627CD4B40481C4CFDD3E534C0DA95C54B30CD4B401D44807DE5E534C03FB12B3734CD4B40071A9A29EEE534C0271197E234CD4B404F8624A9EFE534C0C9FAA8943ACD4B405FE0358CFCE534C0BBB60A3D3BCD4B403216790DFEE534C0D43A91463CCD4B403705D77300E634C07E9A4A4D42CD4B40D32E6DF60CE634C0FC55982348CD4B400ECA15F214E634C0903ACE1F49CD4B403CE0337A16E634C0C83F420453CD4B40B812224728E634C0025156BE54CD4B40C83210712CE634C06DD4B17255CD4B401A2937AE2DE634C06C09AF3058CD4B404C4A56D131E634C078F01E7458CD4B405CEE413C32E634C0A3AFD9B25DCD4B403EF1793038E634C03DA80AA25FCD4B40E65ABD3C3BE634C080EA1A6261CD4B40EC92FB5C3DE634C096B8658861CD4B4023CE8B8F3DE634C0D717175B63CD4B40ACE31BA33FE634C0179C78F569CD4B40159D9DE347E634C0D5B4F6FA6ACD4B408981064949E634C05E26926970CD4B403C5A3B594FE634C014823B3A74CD4B40D67CEE0E54E634C0AD9FF4C275CD4B40187C25FE55E634C0BE17870179CD4B407B3CC02B5AE634C0BA8A95D47ACD4B40B68F93805CE634C059AB9B1E7ECD4B4016A8016660E634C0C14C2ECF7FCD4B40BB83957262E634C00DB8BD2D84CD4B40A5C7F3DE67E634C089263D4487CD4B40B9C70D576CE634C0AF4A0BAF90CD4B40810050ED76E634C07270037B91CD4B4000E49F1E78E634C022F0906693CD4B40706D3B447AE634C033287ABD99CD4B40F30E2A467EE634C064575E29A8CD4B40AADF9E068DE634C0646D2545ADCD4B403C8E026493E634C03D8852E7ADCD4B402AC52C3894E634C05A68739BB3CD4B4005449DFF98E634C01274A9D0BACD4B4012DC8170A0E634C057259906C1CD4B40E2FC4272A7E634C0FC375E03C4CD4B4062BED31DABE634C014DBE4F4C5CD4B4097E8DBAEACE634C0BD58AC3BCBCD4B40FD804478B1E634C0CCA4CF31D8CD4B404842C1B7BFE634C02A245C83E0CD4B4086408F93CCE634C059CD6C69E2CD4B405C950998CEE634C0ADD370C9E2CD4B402BC350FCCEE634C0AE5D36FBE2CD4B408E4EF232CFE634C0EED8531EEDCD4B4042336DF8D9E634C03297BDA7EECD4B4050BA19EADBE634C0DDDB58F2F2CD4B4072BB7B0DE0E634C07782C889FACD4B40922A2474E8E634C09D7E9D07FBCD4B40A0B0B1E7E8E634C02933608600CE4B4050FA89EAEDE634C0E45FBB4401CE4B40C371119AEEE634C0D4E1924505CE4B406EF99355F2E634C000D5785C08CE4B40225E140FF5E634C0AA4B87020DCE4B4019864406F9E634C0713BA96414CE4B408D2D5B1200E734C0F666269C15CE4B406B0EB25C01E734C0C5CD07701DCE4B4076992E7E07E734C08A4F3AB821CE4B40394D21660BE734C07B56895526CE4B40EB303FE80FE734C0462BD74A28CE4B400D766EF311E734C0BDFAEC1E2CCE4B4067FAD89C15E734C0F3F8CC452DCE4B40FA425A9616E734C05A8A40EA2FCE4B406CC32A4C18E734C0F2DD641536CE4B40754AC4BF1CE734C0F2A6D3FB38CE4B407C2151101FE734C00B3A8A413CCE4B40099EB8F321E734C012E2FDCD3CCE4B40D7945B5322E734C080B7F9444CCE4B40B030B2F02FE734C0B3E6313F50CE4B408F9B324E34E734C01A0F884C53CE4B4088EC434037E734C0321E0D8654CE4B4003C84E7438E734C09A4BBA5C58CE4B408451134B3CE734C07B4017375ECE4B4028B7248041E734C0A2C4481263CE4B40114AD20A47E734C04A37468A69CE4B40D4814E944CE734C0DC6EEC0671CE4B400A0957AE53E734C04E9DF03C7BCE4B40DA633CED5EE734C0CBEF9AAB7FCE4B40A340885465E734C0EEA9A40C82CE4B404CD9ACED67E734C04C1575D585CE4B40533DBE496CE734C0A57692098CCE4B40B07D1AD173E734C00D66AF9E95CE4B40F79056857DE734C05DDF93B695CE4B40311FB0A37DE734C0CB6DCFD595CE4B402EECE8C47DE734C0DF64869596CE4B40A38756877EE734C06DAC570D98CE4B40E72A7BD57FE734C0D2B2AA6EA1CE4B40BF1BAE6D89E734C07301916DA6CE4B40E594B73F8FE734C09D3672F8A7CE4B40C1651FCD90E734C004E14548ACCE4B4034C8AEA094E734C00596F66DAFCE4B401AE1628E97E734C0DC11121BB7CE4B40CF5E2F099FE734C044E1670DBDCE4B40DBB61C7EA4E734C0B53C3FA8C1CE4B404B95929CA7E734C06045C939C7CE4B40B78FBB4FADE734C02ACACC3DCCCE4B40CD3B0C71B0E734C0D35B379CCDCE4B40060F5751B1E734C04E1828B3DBCE4B40FABF1090BAE734C06F585B3EDFCE4B40D4E1DD09BDE734C0775D352CE3CE4B403FC2F4F3BFE734C0B327E448F1CE4B40768B1FF0BFE734C0BD4F1F7700CF4B40549FAA9CC4E734C07D1F950A0FCF4B40D89B3ADACDE734C0EB5FB75B0FCF4B402B98392BCEE734C0240867491ACF4B40D6B8B430D1E734C0ECAD78882BCF4B40B3352DA7DCE734C0B042F6F92CCF4B40B0756EEBDDE734C0831C15CA39CF4B402D23C58AEBE734C007073D5245CF4B400B811DBDFCE734C065D9CB4A4FCF4B4046F9A51711E834C06923134A50CF4B40B133057A13E834C0C8884D3255CF4B40A7ADEF4F21E834C0E92B80005DCF4B4032DB4E612BE834C081D14B5668CF4B407806FA9E39E834C01B1FCD976ECF4B407FC69C3242E834C0D774FEF870CF4B400484F2BF45E834C03D120D5879CF4B404F1AB7504FE834C0D3B720847CCF4B40470EB81A53E834C0037341B485CF4B40B65548915EE834C0B4C85F6C8CCF4B404450385266E834C06DB97D3292CF4B4017DCB83C6CE834C0ED89FAF496CF4B40A9F9D75270E834C013F1328E9DCF4B40AD7ECA5B74E834C0667BDE60A1CF4B401A4E21DE76E834C0F4BDD78EA6CF4B40F754C7807AE834C0E6606D38A9CF4B40C4B18B5A7BE834C0341B9FD0ACCF4B405D3814D67CE834C02480D38CB3CF4B4011563B987BE834C04E7BF45BB7CF4B406E1975937BE834C0435DEF80B9CF4B405B0DEE687BE834C0F2376C8EBECF4B40A03ABC107AE834C00B7341A0C5CF4B40931D3E8777E834C0A750E7DBCFCF4B407C3468EF74E834C0646DA720E2CF4B4098E5463472E834C0D1EC5B1BE4CF4B40781394F271E834C00FDEA054EACF4B40F7C18D4371E834C08856CBA3ECCF4B406CD083FD70E834C0EF6932E6ECCF4B40BE402BEC70E834C02D47F5FCF6CF4B408237EF506FE834C0265447C4F7CF4B405D4E58456FE834C06B3F9FE1F8CF4B40883AF1076FE834C050D5C85F04D04B40FC4A2BE26DE834C0B2E321DB0FD04B4049E4A95E6FE834C0BC7DD89E11D04B40B03F63CD6FE834C096748AF219D04B400E3E5BDC6FE834C027950CD720D04B40D8EBDC6170E834C04D7550E829D04B404BC54BB171E834C0D1144AC62DD04B400F2790B171E834C08C64A54530D04B4021369DC171E834C0CDDBF95732D04B40F3871ADC71E834C0B611E47535D04B404080380D71E834C04B4CAADB39D04B400A4B7A6C70E834C025F7B69E3DD04B40A8D3340770E834C0B459540E43D04B40E5431DA96FE834C05705A7BC44D04B409EA950726FE834C08D47266747D04B40C57798A96EE834C0A793736953D04B40FF4D1D986CE834C0CC42A8D056D04B40DBD6006B6CE834C0ED770C4563D04B40D6927BE96DE834C03ACE130C64D04B40F9F5B5C46DE834C0B5FBB59468D04B40D973D8336CE834C0011184C97AD04B4007D9F2956CE834C0A21D610B7FD04B40A26F58736DE834C01359810E81D04B4091190C736DE834C075EEDDAB82D04B4055C871796DE834C0ECDCCB7E86D04B40F11A50986DE834C0188ED3E794D04B40176CE52170E834C0CEBAD1F0A2D04B40481977C676E834C00EC7CD5AA5D04B40BA54AD4B78E834C08391748CA7D04B40C115E58F79E834C0CA7CA81CAAD04B40C250C0AB7AE834C0ED22A416AAD04B400487E0DC7AE834C050A736F8B8D04B409F73479C81E834C0E835BE52BFD04B40B1F269E785E834C0A7361C4DCED04B40A939ACE692E834C0BEF27A26D3D04B401EB7073C99E834C0E401B53CDFD04B4062AB5990A0E834C01636154DE0D04B408D7EFE71A1E834C0A68B706FE0D04B400AE48A8EA1E834C0F714B87BE1D04B401BB1BEF8A1E834C0E81AAA1BE4D04B40BACD3D3DA3E834C00FCF750EF1D04B4090794668ABE834C05F5B0FF2F6D04B40BE6F6109B0E834C0E33E9180F7D04B407F31697AB0E834C068F0EF74FAD04B40DB49B0D7B2E834C0B2DF350A07D14B4036F8FEA8B9E834C0BE84E60909D14B40B1631F18BBE834C07832A0830AD14B40B7B863DDBBE834C0B00E543912D14B40ADC0BB91C0E834C01591C29517D14B406D6C4B53C4E834C0063972EF1DD14B40FE47FF9EC8E834C00455FA861FD14B40E50A4192C9E834C09DD5D7811FD14B404C88A2B0C9E834C02090F8242ED14B4047C754F6D2E834C002243CB430D14B4054CD5828D5E834C0DDE427DF31D14B40AC2E67EFD5E834C0F5ADBED935D14B40523264BAD8E834C07310FDAD37D14B40279C7316DAE834C0B27024703ED14B408A3A2843DDE834C0C139419F43D14B409F7EDA5EE0E834C025F23F1E4AD14B40783644C3E4E834C0851F35C54CD14B40F400B5C3E6E834C05FBED8C653D14B404F8B2669EAE834C0744715795AD14B4079B29E67EEE834C0A81A484360D14B409102164FF2E834C043AF83E361D14B4007A55A29F3E834C059F06FAE62D14B40820B637AF3E834C0222A5F8464D14B407B00344BF3E834C03D85630B71D14B400D948A2CF5E834C0384DDE4473D14B40184E2ACAF5E834C0A955743475D14B40F9CEB6B2F6E834C07703F14979D14B40D181F704F7E834C0BA8288217CD14B404EAB3953F6E834C0828B4C2880D14B4087D28592F5E834C0EB0694278FD14B40E7504504F5E834C008C1639E96D14B40557E00DBF5E834C0F85170D69DD14B40DA193060F6E834C0D0849A0DABD14B4015F62516F9E834C0E74E8A42B0D14B40534FAADBFAE834C002564AADB8D14B40E0B4C5C1F8E834C07AE8747DC8D14B40210A69D0F9E834C0E2A6B764D1D14B404FA626D2FBE834C087DE01D5D3D14B4022EB606EFCE834C0409A2543D8D14B408A657AA6FDE834C0748CE396D8D14B40BFDE78C2FDE834C07665D61DDCD14B40610C27F7FDE834C0194C8CF7EAD14B40B79C660D01E934C041B8E1CDEBD14B40A8A0915A01E934C032EFE505F4D14B40DD96963102E934C065208B59F8D14B4016CA7C4F03E934C0DF07CA4C0DD24B40EBD7A7690DE934C0B45D43A111D24B406251B08710E934C0B64AD2F415D24B405C8F9CE912E934C040CF787717D24B407F8123C513E934C04036FBB71ED24B40B3B20F0318E934C08FBC3E5C1FD24B404E93596418E934C026D46A6524D24B407D9A60691BE934C03FF362B826D24B40474041971CE934C070D7FE1236D24B40ED232B3E27E934C0BC90D7E13ED24B40CF5B8CC02AE934C06B48FD5E42D24B40147D568C2CE934C077E0417949D24B401F2CDDC630E934C0DF42D18F4ED24B40FDEAF33934E934C09AC865935CD24B408EFAECEB40E934C08E7584E75CD24B40A6C7260B41E934C0D37471745FD24B403AC9270F42E934C05D3D032365D24B40C0E3E57944E934C0181B50A173D24B40DF64EAFD4CE934C0D17060AF76D24B401AF8D04E4FE934C027E309E681D24B4064087D7859E934C0D9A286FF81D24B4094C49C9359E934C003A71BDD83D24B407EE9ABD55AE934C07AC4F46E8CD24B40687BD4EF61E934C064AE2B398FD24B402BDE0C6663E934C02988648295D24B40B21AB06968E934C021DEABE09BD24B4022BC0A256BE934C02843F940A5D24B403BFD42A372E934C06F00C643A5D24B40E64E2F9672E934C010BA28BCA7D24B40DC7B567474E934C0781D478DAED24B40E742849478E934C09D34BA16B3D24B4058CF261C7BE934C062D50704B5D24B40114A043A7CE934C05E9FB374BCD24B40EAFEB5B380E934C02C960142C4D24B40F079FA1D86E934C0EEA33D58CCD24B40EF78CD808CE934C0C4312568D3D24B40B58686BC92E934C0635D3D95D4D24B40DD0F61E393E934C0CC46664DDBD24B4084B1C31399E934C02C1A6A6EDBD24B400C37D03599E934C07C72D4DBE4D24B405BA140B09CE934C0F37CCE77EFD24B408FF3C41DA3E934C0B3474169F8D24B40D463E4A6A9E934C0298A0B0600D34B40F43C02F4AFE934C04F5DBA8D03D34B405BC61E3BB3E934C0A2AF729F03D34B408A1C8849B3E934C0139C1FAD05D34B402FA643B7B4E934C0ABDEC5290DD34B40E5E5B29FBAE934C059EAD83813D34B404001CCFDBFE934C0F763B9DA17D34B40E0448ED3C3E934C023688D4E1DD34B401D20CDBCC8E934C052B02DC222D34B40B8A3B00ECEE934C085B459F526D34B400218DD69D2E934C0DFF046842BD34B4036AFDD6DD7E934C0CE6D898F30D34B4068E2B696DDE934C0F3867CCD31D34B4059B3CBA7DEE934C03ADF020B33D34B4059350322DFE934C035D0401041D34B40D6669712E9E934C0040BB4CF45D34B40D2713146EDE934C004625E164AD34B40DD049B45F2E934C0481C8C104DD34B403D06E008F4E934C02E25125459D34B40FC0E3A1BFDE934C0E3DE28CD64D34B40FB28BA4B09EA34C0FFC8BEA269D34B404F4FEE4D0FEA34C0AD245FCD70D34B40351FC4E119EA34C0F85D0D4871D34B402F80E07D1AEA34C092A1795F72D34B4064DD40331CEA34C0B7EBCCC972D34B40AC552FD01CEA34C03DA4D1A773D34B402B2BEB7D1DEA34C0D8428FA773D34B4008BD167F1DEA34C074E63EA277D34B40545B56F11FEA34C043AD80077DD34B40139B507324EA34C0A31B750989D34B400A92847F30EA34C0BBC7ADEA8CD34B40A081291835EA34C091DA26DF92D34B40C657B3643DEA34C03508B6A494D34B408D82B3E13EEA34C0ECC9AC309AD34B4014D4660646EA34C01265C125A6D34B400E14F48F51EA34C03734F172B2D34B403583B7A462EA34C06219DDEFB7D34B4019A43B3A6DEA34C039B23D57BAD34B40A8B4A7FA70EA34C0964F5E93BCD34B40F29B3D9474EA34C03526C059BED34B401FE18A8777EA34C09A8921B5BED34B40929840FD77EA34C0BCA24BDBC4D34B40AE9C4ED17DEA34C0B54D0C5BCAD34B40BCAA61D683EA34C07A9F5CC8CED34B405BE0374F88EA34C0E97C4F5FDBD34B40CF4B2D8F97EA34C0B00AFE8DDFD34B4036E2B38E9DEA34C0893C78AEE9D34B4091A1195DAEEA34C001755B30EAD34B40906B5D7CAFEA34C0F1BDF0B1ECD34B40DBA805EEB1EA34C05ADC42EDF8D34B40CF60692DC0EA34C0412E2754FDD34B40BC31F338C6EA34C09CD0BAB704D44B40EE378726D0EA34C06BBD310F0AD44B40C05150C8D5EA34C0A767803E0ED44B40E9EDD305DBEA34C08AF4729117D44B40745C4EF8E3EA34C08AE07F8922D44B4093CFEE81F2EA34C05FBAFDA22BD44B401C63987C00EB34C0DFA7D0D52BD44B402CBAE3CA00EB34C07FEEFA492FD44B40BFDCB82006EB34C08542646039D44B406AAA717210EB34C04941F4063FD44B4094DD395117EB34C0A62B442241D44B40CDC694D719EB34C08111AB2D44D44B40E97407481CEB34C0F895873A49D44B40F406AA4921EB34C020176B9E4CD44B408659F7EF23EB34C04ECC0E3358D44B40413C95CA2EEB34C057E5DE255AD44B4015B66C4B31EB34C0D0E6056E5FD44B40196F271A36EB34C0AB7A82B665D44B40C27F7C223EEB34C07843BC426FD44B40DEE9CB1445EB34C070F547867ED44B40946DE31357EB34C038D3572C82D44B403530655A5CEB34C0F9CB661888D44B40339E264C63EB34C0BFA6AE4791D44B40BB81F88D6FEB34C0C188980699D44B40F678A7417BEB34C092453FBC9AD44B40BB08D9E67DEB34C00E915D709CD44B40F712F09980EB34C0B9E2BE2A9DD44B408520303F81EB34C0ACE677FBA4D44B40187B20068CEB34C049F7A43CACD44B40F968C94892EB34C05E8D75A8B7D44B403C874E4DA1EB34C0542D6D62B9D44B40696907D7A2EB34C0FBC6D4A5BCD44B40B7B34C8EA6EB34C07D904FB3C6D44B40C35E1CC0B3EB34C0D567B0B7CAD44B40C57FB2C9B9EB34C01C405A8ACBD44B402FBFBDB7BAEB34C018C593C5D7D44B40C5109B29CBEB34C019C5610ADAD44B40AD5129C2CEEB34C092ADD523E1D44B401D77D7B6D4EB34C0902B016BE4D44B4014C76CF6D7EB34C06A53E436F3D44B4052490C23EAEB34C0E868ED86F3D44B403BA2D59AEAEB34C0E31AE443F5D44B40AD2FBCA9ECEB34C06ADB45DDF9D44B4013452B6FF1EB34C04DC9AB4CFDD44B400F434D2DF5EB34C0D459832700D54B40C7669B70F8EB34C03DD13CA100D54B40E4558BE8F8EB34C0F295E0FD03D54B400650FDEDFAEB34C0278014390CD54B40AFB9762901EC34C0DC82DD2516D54B40F9B4727A08EC34C0E02BD33A19D54B4048DEDA330BEC34C0EB9FF0691BD54B40F69DAC320DEC34C0DD63DC0C1FD54B40370C25A110EC34C01B3DD0F427D54B40982E72341AEC34C0040B8FDC2AD54B4013A22ABC1DEC34C0A402283C2CD54B40EA75E3CE1EEC34C09A0C58A931D54B403CF8E48B23EC34C0D4A9ADAA3BD54B4091AD3FD42DEC34C0AAAE14D93ED54B409D76797F30EC34C0B340D47B47D54B4089C290CB39EC34C0FFC9B0F747D54B401538143D3AEC34C003876DC448D54B40D0ABF6FA3AEC34C085B6F1C94BD54B4043A1E9D03DEC34C0E895397A4ED54B40A500FB7040EC34C0DB86FE9D51D54B40AFB7D2A043EC34C023B231965ED54B40EBCFB47F53EC34C02A2308016AD54B4077D44F2267EC34C07AC7788C6FD54B40D1B0D26674EC34C02024B5F671D54B40AE7AF98E79EC34C0AC48C13C74D54B40F21B7C997EEC34C0A0856A3A76D54B40933C152F83EC34C07667F54F79D54B40C6592AF489EC34C00D90F46E7ED54B401C23359E91EC34C0A75E48EF81D54B40F4C87CD797EC34C0482916A18BD54B4048EF50ECABEC34C0E43F8F9593D54B40500E158BC2EC34C01EA02EB394D54B40EF842250C6EC34C075BC410C96D54B404FDF9FA4C9EC34C0E90C894D99D54B404A46DE32D2EC34C0C5164FF2A0D54B40F0FD189AE7EC34C058DE0268A8D54B40F0C34DA700ED34C05D4071BCADD54B406951EA8816ED34C0BBCABFE3B0D54B40C2C25E8221ED34C07C221EA6B1D54B40CEF4E63424ED34C0A98A6A28B6D54B403A5B699034ED34C06B390982B9D54B40865FE01342ED34C09AC512C3BFD54B40E20A15595EED34C0CDA65296C3D54B40C275DC346DED34C03F045D2EC5D54B40D8A223BB73ED34C036DB96C6C7D54B405AF3B9F17EED34C0AF3C7B7ECAD54B4011FEAD3188ED34C0810FDFFBCBD54B40B3A8A8788DED34C0F0EEF545CFD54B40985BB59C99ED34C0AC1B0536D4D54B40222C1A49AFED34C07A056E66D5D54B40EAE91CA4B5ED34C0F855AFB1D5D54B403D4CB4BFB6ED34C057F4EE98D8D54B406387DE15C1ED34C0A904B5E7DDD54B4024ABC2C9D8ED34C05128371EDED54B40EF700889D9ED34C025F9CF25E2D54B40253B6628E7ED34C005FBA838E6D54B40928053D9F6ED34C0CF3B6E86EED54B40709FBD440FEE34C051F13EFEF1D54B4067DA26BF16EE34C0ECD9DF4BF5D54B404514A10E1FEE34C018B2C741F8D54B40CBC495F126EE34C04AE6802EFBD54B403BC8C7332FEE34C07EC42375FBD54B4024DFE1EA2FEE34C0D0AB16F8FED54B40F7B6218B35EE34C042D1A0B102D64B403523C5D63BEE34C0D273EE310DD64B404D5E9B8E4EEE34C0CBBF42470FD64B4084A417A052EE34C06AF0135011D64B4032B0DF5655EE34C04874D83817D64B405A45FC995DEE34C00C10B5FA21D64B4012B139286FEE34C038ACC6352BD64B4096512E8D83EE34C0DF5B891B30D64B40DB31DB3090EE34C05091F74630D64B409E1A09A390EE34C07460CF7331D64B40C5147F3F93EE34C00922D5A935D64B40135A060F9CEE34C0B578283336D64B40778FD3309DEE34C071D75C7C3AD64B4002984550A6EE34C0A5126A2C3DD64B40FCDDED07ACEE34C0A8E85D873FD64B40428E6A3DB1EE34C0B1AB5C8842D64B406AB1FD25B8EE34C0880DADCC42D64B40FA7DD2C3B8EE34C068169B1045D64B405DD0D105BEEE34C0E54A636446D64B40F56EF509C1EE34C0B18C7A8546D64B40A907784DC1EE34C003A140A449D64B4073662046C8EE34C021B6EDF44AD64B40416F527DCAEE34C0F93039B84ED64B406092FC2FD1EE34C00C17302754D64B4008F98A67DBEE34C046AEE4965ED64B40DB41F3FAEDEE34C069B35A4961D64B40CC863BB9F3EE34C0CE8E773E65D64B40CC7EBB5CFDEE34C0C962294C6DD64B40363AAF4D09EF34C0CEFF713E76D64B4047945AC61AEF34C0B5BAE8AB79D64B408BDF059E23EF34C087FF8D4E7ED64B409A7C2F522CEF34C03F73E7F780D64B40AB5CA28631EF34C0EC2BBC768AD64B40244AECDD44EF34C0093D40488FD64B40E17C8E444EEF34C07F9E69CA92D64B4071593A0955EF34C02DE2876294D64B40C05567A857EF34C0FB1A73A197D64B40EB507E405DEF34C0C90E30B3A1D64B40658B976F6FEF34C0C388C30DA4D64B403129B3D673EF34C0583982D0ABD64B4021DEC7DF82EF34C0DFD4859FB3D64B40E474047891EF34C0133F7E8DB5D64B406BE3A42E95EF34C0C54277D5BDD64B403F698297A5EF34C0519256D7C4D64B40AC16CB59B5EF34C0E494FC2CC5D64B40C98AD800B6EF34C04E36524EC5D64B40FCA81838B6EF34C0095934FCCDD64B408AA067D8C3EF34C01011082CD5D64B409BB0875FD0EF34C0BD609C34DAD64B4000217F1BDAEF34C0D7150B86DAD64B406A3FC4BEDAEF34C0871663FCE4D64B4045C3991FEBEF34C0C2641EDEEBD64B403EED4907F7EF34C0BE8F0824EED64B403496D45EFBEF34C069E6760CF0D64B40256BE827FEEF34C0F75B2D9DF1D64B406F5FCB7D00F034C073C6DB6CFCD64B40717BBCF910F034C0BAAB4C9E07D74B4048F4B31D25F034C00A420D0C08D74B409E1A7C0626F034C08A13309E10D74B4059520C1735F034C0F28BF4B514D74B40441650853EF034C0A6E0153717D74B405DA9DED042F034C02596D9831BD74B40DBC5A73D4BF034C065939D521ED74B4080A6A89950F034C00D461B6527D74B405A5A396E60F034C00FE60B7928D74B404C3F575762F034C064A6D8D02DD74B400B142FF66BF034C00005581830D74B40592C1F3570F034C011C88A0D35D74B406BE20AC379F034C0716E256236D74B40756112077CF034C06765714F38D74B40D395B2047FF034C0251942F439D74B40C4F2D74181F034C01A09C79C3DD74B401644E37F86F034C0CF844E7940D74B4016359BCF8AF034C00D55E6CB41D74B40AB6067548CF034C079B5F8944BD74B409B2FFC3A99F034C0F6E3E3FD5DD74B40C04D71ECB4F034C09D859B486BD74B40F92E5318C6F034C03DB5503C6DD74B402D0784AFC8F034C0CE10DAA183D74B40CF70BE36E7F034C09B7811ED90D74B405811B62EFDF034C04B26C07396D74B4015D4EB3308F134C0B1E3306897D74B403E50B3220AF134C0DCF757749BD74B4070027C7312F134C0A419BAF39CD74B40314545E815F134C027991353A0D74B40BC927D151BF134C070569E22A9D74B40A0DEC2E52CF134C01ACBFEE6ACD74B406B2EA2FC36F134C0FA3D1BA8AFD74B40209C7F6A3CF134C06ADAAB42B8D74B4037AEE30150F134C0727A55A4BDD74B401F9E9A385EF134C0F1558822C2D74B4073CA4A9869F134C09313738BCBD74B409C2190F786F134C06A95F810CDD74B40BE487DF58CF134C033C62DDBD3D74B40672C0B98A4F134C0EE19690CD6D74B407DA9777BAEF134C042099307DAD74B403FD1F1AABDF134C0CD1F926EDDD74B40F98E5343CCF134C0E2455EBFDFD74B4007177F8CD7F134C00D9BF250E2D74B40CDC05378E1F134C0FA4322F6E5D74B406DE99C81F1F134C0DA206CEEE6D74B40054ED76DF6F134C0A25C1E04E8D74B407C1E4A2BFBF134C07D661F62EAD74B404F29FE5803F234C04BF21787EED74B40A327D77210F234C088F4F0D4EED74B400947B56A11F234C01A4CC3B3F1D74B40B3A5649F1AF234C03611407DF8D74B405414604E2DF234C04CF3E291F8D74B40418646872DF234C05796B613FCD74B40DEDADF3637F234C04239897500D84B400177300142F234C09F93E94804D84B408C7D34244CF234C06E2E851D09D84B4014667DF059F234C0CA1130CD0AD84B40A9C168EF5EF234C0B7C2A5B20DD84B4038D83DD767F234C0783E635118D84B401056F0207CF234C024CD924720D84B40CECF7D708DF234C0460B17A922D84B40A4C83E3694F234C0ACB7271724D84B40FD653DDE96F234C0DD4DE7C12CD84B4051BE84E3ACF234C09AECC4BD2FD84B404269E7D6B5F234C0879E586132D84B405871F605BBF234C0C421AE8D39D84B40FDC45BB7CAF234C0CE2C60723CD84B400232605DD1F234C07C2A34DB40D84B40560A2D03DCF234C0F24ED63945D84B40797BA879E7F234C0AD596F4D4AD84B40740CBBF9F5F234C0E5C3EADB51D84B40FDF97B1510F334C0449A6E2355D84B4015BD43181EF334C0DEB46C9958D84B40BBC5003B2FF334C0846C6F8E59D84B4038A6D5D234F334C0C739E1825AD84B40143CA31F39F334C0008C1E1A5CD84B401A4008C840F334C0AAE566CB61D84B40CE7FD72C5EF334C02AF76EA463D84B409C47BDBB68F334C0108F497366D84B401A4B31A67AF334C069C0902D6AD84B4078045AE58DF334C0A389A0A76BD84B407AF2A7AE94F334C02446A0B96DD84B409612210B9CF334C08F8EC5856FD84B40EF05C0C6A2F334C009C9538573D84B40B63BAB94B2F334C094481DC978D84B404E5CEF35C4F334C0D17520207DD84B405A17FD05D8F334C06AE553CD7DD84B40904B47C1DAF334C03F0651ED7FD84B40FC62A72DE3F334C00653746686D84B40C8B910DFF8F334C04E186DF78AD84B401109A05B0AF434C0195CE1FE8AD84B40387CA27C0AF434C0A69010E58CD84B40A965D7BC0FF434C0BDB9B5EA93D84B400938B50E2EF434C084EE4DAE95D84B40A17C3F2738F434C0ED49513F9AD84B406E09F12C44F434C05641C6249CD84B4047B61F294AF434C0F821DF8AA2D84B407CFA536162F434C0432B450DA4D84B40911889486BF434C0C915FF52A4D84B403A3F28736CF434C072D729E4A4D84B40E61E400E6FF434C0839014E3A6D84B40267A98BB74F434C0C65FDA6EA7D84B406342CB4D76F434C0764460EFACD84B40153BFC5186F434C02C2EEEC0AFD84B40F94B61118FF434C0C36CEEECB2D84B40811F7C9599F434C0390468F5B4D84B4050F557B2A0F434C0B9A77F57B5D84B408F5D0F1DA2F434C02B3226A2BDD84B4069BD8696BCF434C027178984BED84B40937B3FF0BFF434C0ED2D758FC0D84B40DC3A71C4C4F434C0ECB3B4B9C7D84B40C3176F5DDCF434C01CA48319C9D84B40FF8DABD2E1F434C02FCF58E2CAD84B40E916A691E8F434C0607F55ADCBD84B403DC6DFA3EBF434C0556968E6CDD84B40CE4D1677F4F434C0FE96DCD5CFD84B400424B004FBF434C0034E29F7D0D84B4062E7D337FFF434C0A05677E3D2D84B40E41C6E8904F534C0C21F2EA3D3D84B40E431E1A306F534C078328BDFD3D84B4020110F5007F534C07F407F38D4D84B408E74F4F207F534C0D0FB36E8D8D84B4040BC852B11F534C0FD0F450FDED84B407143CE101CF534C0B5E9738BE9D84B4079A35D2A3AF534C05ED09EFCEBD84B40FB6AA23942F534C00589B705EFD84B40E0AC081A4DF534C0D1FFDCE6EFD84B4079E3668950F534C07B3C3E10F3D84B40EF292A9257F534C0F3163930F6D84B408F5FA8E85EF534C0B99E5EF3F6D84B408D1E9BCC60F534C0AC06D550F8D84B401EB489B563F534C05AEAB4AE00D94B40B253356C78F534C0727302B107D94B4067B7F2B38CF534C0303B58430DD94B40D43980779CF534C08E3608CA0ED94B40C16483EDA0F534C077093E5D1AD94B40B78947E6C3F534C0123A8FAD1AD94B405EB5A8DAC4F534C058CDC63821D94B4011C0FFE7D8F534C09BC4BD4322D94B40ECB81F2EDCF534C0C3D2345626D94B40B7FC2248E9F534C0DE222EFA29D94B4043EF7FA3F4F534C0A60B92862BD94B401CF300A7F9F534C0912EA0732CD94B40E22247C4FCF534C0944D55A62ED94B40FEB9D00802F634C05E703A1434D94B40531C9CBB10F634C04489961934D94B40C30067C710F634C017D91E9737D94B402D19BD4D18F634C08A5DFDE738D94B40B602E8331BF634C0CF1C57C03FD94B402C72809D2AF634C0328852AA41D94B40B2C484992DF634C0FD7A460B45D94B408D414D5A33F634C0A3DE3F6950D94B40859B92784AF634C01EEE352B51D94B40EAE8F1504CF634C034AD3B1855D94B40A2E1F2CF53F634C00CD3456B58D94B409D7BE97E5AF634C0B08C81E65CD94B40A010D3FB63F634C06D0B73F261D94B404401D99F6FF634C08E4A0C8562D94B408DEBCE1071F634C0D7A37DB263D94B40107DBE7773F634C061E6463065D94B4013E0409576F634C0B62217486AD94B40896D3E7A81F634C0C0ED877772D94B404B649BBF95F634C0A71631A273D94B40D062151D99F634C0E4EE7CFE75D94B40D05272959EF634C000A239E27ED94B404FE5611FB7F634C0759BB9C383D94B4065E6AC5DC7F634C0B83ACFC284D94B40C6E0B1C4CAF634C08CA6964287D94B4047610087D3F634C04FE8CCEB8BD94B40C99E5F45E8F634C0DFD95CC08CD94B409A3C7B53EBF634C09731C2708DD94B40A3CEC7E9EDF634C0FAB871F08FD94B40D72C3E7DF7F634C0EEF1726793D94B4011D58E7906F734C0FD0F8EC495D94B403E96832312F734C02EAC856E96D94B406AB6828415F734C0A867BBA398D94B40073B4A1D21F734C03A460ED09AD94B4058DC13922BF734C00793CBF49DD94B403BADB86F3DF734C0CBAD22C99FD94B4085C3E4094AF734C06FA71237A1D94B40F534BC4351F734C0091175BCA2D94B406758D54E58F734C00C722EE6A2D94B4055FAE6E558F734C06A956ED4A7D94B408B6CD8FE6DF734C0ABAEDE43ABD94B405BDD4E1D84F734C0F644E0E8ABD94B400510378E89F734C0F4DCA2D3ACD94B4037C72BDF8EF734C074F4A543AFD94B4064821B65A0F734C047D77ABCB1D94B4064CE5694B0F734C052BC684DB2D94B40F90D4A22B6F734C05F3D5832B4D94B40C0CB86EFC3F734C014F2FD87B6D94B40DE684156DBF734C01BE5A90DB7D94B40BDE51999E3F734C0E5480260B7D94B40BF3E60D5E9F734C0736557B6B9D94B40EA1B273AFAF734C0C0D9771FBAD94B408062291EFFF734C0B4EED836BBD94B401E6A7E4A14F834C05724F169BBD94B40D35397871EF834C0088AAEA9BBD94B4061CB5A8626F834C0E5A2D1AABCD94B400323EAD52EF834C0B917A5C1BDD94B408C26DA5536F834C097F63077C0D94B4028B760B450F834C0C1D03090C0D94B4072C4AC5E55F834C09D4AD34BC2D94B40228742F85FF834C0067819E0C2D94B406E82FC2064F834C0C0A0766FC4D94B40624092EB6BF834C0541DE8C1C5D94B40B79D40F872F834C0560118B1C8D94B40880CE1BB83F834C0694A18F2CBD94B405A7950F19BF834C09DC62BFBCBD94B40455F9C909CF834C0828CBBEDCCD94B40AE42A9A2A2F834C06A06ABF9CED94B40A3A9C45FB2F834C0E5B8404CCFD94B40377C8D75B5F834C08A5C4A38D2D94B40D68DC0AECBF834C0268452DDD3D94B40DB86DE82DEF834C07FE61CC5D5D94B4062E8563CF3F834C0B5674D51D7D94B407D8F4D4B02F934C0284271B9D7D94B40A9C8EF8606F934C0DEB40FC8D8D94B40F0B6F25712F934C0A21B46E8D9D94B40DA2F755F1AF934C0759B291FDBD94B402730155D26F934C0AA4D4211DCD94B4077A1502D2BF934C08B18C4E6DED94B403B315D4A44F934C0C41D6DFEDED94B407A2E72FD46F934C079100911E0D94B40B2DB0B6F4EF934C084570BE1E0D94B40321C952758F934C0E86942F3E1D94B407BDE34CE61F934C0C4365E46E5D94B407320614D75F934C035330145E7D94B401E0D64D082F934C0BFC0A6C6E8D94B4025E60FD88EF934C0C1CB5ACDE8D94B40403E78008FF934C09CD759CBE9D94B409A8EE85E97F934C0A867E904EAD94B40A1B4939298F934C09F43E45BEDD94B403A5AF60BAFF934C01F715958EED94B404698EEE3B7F934C02C426B60EFD94B405D41A6DCBDF934C0F1E0574DF2D94B407627303ED3F934C0121AAFD0F2D94B403E584745D8F934C01493F5D5F2D94B40CB50C269D8F934C06398F0F9F3D94B40E53F3F80E1F934C0512AC507F7D94B4045885842F0F934C0B9AA9DF4F9D94B4021BF5DBD00FA34C081688D60FBD94B409996FE470AFA34C05BC6FEB2FCD94B40BCF102FC10FA34C0E88816C8FDD94B402B8B2BC716FA34C09970FF97FFD94B40FACEC70A21FA34C05D5709FF01DA4B40DA16512731FA34C045D53B7304DA4B40AA46C13345FA34C0693633B205DA4B40143FEA4851FA34C058AFD89306DA4B4047F301C45BFA34C0703DAAA107DA4B404E4FC1246FFA34C065E9450708DA4B40FBFEE3027FFA34C0BB6EC16D08DA4B40799BAFA28BFA34C05932DE8D08DA4B406CCD7BE990FA34C0D05286EF08DA4B40878482FCA8FA34C0F7E136FA08DA4B40F825C838ABFA34C098FA9DED09DA4B408D33024BB2FA34C043C31FC20CDA4B400AF7EA15D8FA34C0B170641A0DDA4B40C7DA8A41ECFA34C0DE6E831D0DDA4B40906B4205EDFA34C0AC7792510DDA4B401718D9C2FAFA34C0A1A94A720DDA4B4051CDDD8602FB34C0CA36C2580DDA4B405E18F3D711FB34C0E6A981B80CDA4B4009A8150F27FB34C0143987A70CDA4B4026B0F5882EFB34C002AD56A80CDA4B40FA7D6C3D2FFB34C0A15CE9F30CDA4B4094F709AC31FB34C0C045CD380DDA4B405C713B893AFB34C047E023C70DDA4B400E8F1AEB3FFB34C05CF06C830EDA4B4094C7748357FB34C02CA5C4780EDA4B402FC3CA2961FB34C0CF909BE50CDA4B40CEBF320980FB34C0A76A3E500CDA4B40744699F585FB34C08921F8680BDA4B4080CEA7118EFB34C0B03E014F0BDA4B4055C352E28EFB34C045A1F5510BDA4B40BEA6482398FB34C0A6C2C5460BDA4B40F3D210F89DFB34C0FE00FCD90ADA4B4020F31023B8FB34C060F98E890ADA4B406B60422AC2FB34C053DF6CC909DA4B4042EA0951D2FB34C0FB231C2908DA4B40331F8F11E7FB34C001EC03DD06DA4B4098D82FC6F2FB34C0D505C30506DA4B40839AE77FFDFB34C09E6F4C0405DA4B403E21661708FC34C08D127C1604DA4B40CFC0A66810FC34C03EA69BEA02DA4B40DD982EF31EFC34C0DDAE55AE02DA4B40D1E45EB921FC34C005D44CC001DA4B40A4D624262CFC34C04B1A7DA201DA4B403D7AFAFC2EFC34C032CE1BA001DA4B40E179775E2FFC34C0BB59A4CE00DA4B40DAFB2BEA3FFC34C0DE6127E2FED94B40C5CD22AA59FC34C0B869A113FED94B40CAD3951467FC34C0CBD6B4CDFDD94B403AC4A6E773FC34C022C1DDC8FDD94B403B7EE34878FC34C0B46000D0FCD94B405CE0ED998AFC34C0EFE5630CFDD94B40ABA59DA48AFC34C062CCE7E8FCD94B408ABD0D688DFC34C05A24D7C3FCD94B40073B17F492FC34C0835DFAC5FCD94B40BF9D019B97FC34C08B9C80D2FDD94B405EB592FBA5FC34C0CE00A40FFED94B407861BBFCABFC34C01E20FB54FED94B407B7EE9F4B0FC34C08BA54EA1FED94B40D6241BB0B7FC34C07939E1B5FED94B400B78280BBAFC34C012DD9592FFD94B40ACD4D1C2C8FC34C00A406E2200DA4B40A8879E4CDAFC34C08371EC2B00DA4B4027C042DBE0FC34C035CD577E00DA4B40D8A79BD7E8FC34C09B7F967601DA4B4046873CEBFEFC34C0DD8E089B01DA4B40D891A8A202FD34C0627819F801DA4B404A4B00C10DFD34C0F09056C602DA4B4023D28AD023FD34C00A5E66FB02DA4B40C2B3D3DE2CFD34C097C1673C03DA4B4040C8883548FD34C0F58C1C3D03DA4B401E009D8548FD34C0B44D226703DA4B40F3E82E2E5CFD34C0039E506903DA4B40C2F4DB6760FD34C0518ACC5F03DA4B40E99C1D0469FD34C0035716DC03DA4B404CFCEBAA79FD34C00666F3FD03DA4B40515D0CE780FD34C090EB881204DA4B40E7FAFABA8BFD34C0F777F7AA04DA4B4072B4437194FD34C09AF1806D05DA4B407C331482A8FD34C0271FA98D05DA4B4042F9B5F2B8FD34C0A9DEE01306DA4B40FF569261C8FD34C02B9D97E407DA4B4081D8D758E6FD34C0350E071A08DA4B407BDD7F28EAFD34C08348327509DA4B4003FDB1D905FE34C0052A49EE0ADA4B40E39DEA711FFE34C06E83CF610BDA4B40B091DF832AFE34C02456E9DA0BDA4B40915DA52D3EFE34C081A258F30BDA4B4003A7586745FE34C00D23F0030CDA4B401DBBF0B560FE34C0D5863F1D0CDA4B40D5E5E94F73FE34C057996D480CDA4B401FD52DD38CFE34C093E84EBE0CDA4B40BA4F44E8A1FE34C0F23794990CDA4B4009553160B5FE34C00799F75A0CDA4B40B83FBA0DBCFE34C0B0AC00700CDA4B40BCCBDDCCD2FE34C0BBDAAAB40CDA4B40E140F54DE4FE34C05561E5C40DDA4B40A073B42007FF34C0EBE636E80DDA4B400E7656E90DFF34C06E641B410EDA4B4024FD071D30FF34C0155698440EDA4B403E023EF531FF34C033B5C9800EDA4B405C78C8D664FF34C054DA59C70FDA4B40651F2A478DFF34C0E75A52D50FDA4B405AAC2462A0FF34C04BB9BB6E0FDA4B401B1B0DE8AFFF34C08007D1790FDA4B4080786029B2FF34C08FE5B7F10FDA4B4006158890BAFF34C015C3EC7310DA4B400A4AC22BCBFF34C0AC4C9C9710DA4B404B81F1D4E3FF34C02125022010DA4B40858E3773F7FF34C02C2B4F5C0FDA4B40691A18C2050035C065C1E6190FDA4B40FF8A3575140035C0F9C8978D0FDA4B405E4A76DC250035C088E3869C0FDA4B40C51F9882340035C0BC13FC560FDA4B4088D6509C430035C0175D680E10DA4B408A8544B3500035C0A7F4188B10DA4B40BBCE9746650035C026A5F16310DA4B40E1C4B56B7A0035C0AAF6384010DA4B40B79F5FBB9A0035C0F4677D2E10DA4B40C8D3293DA00035C0D2FCC9EF0FDA4B40A37A45FDAB0035C0DFDC1DE60FDA4B40436A4D53BE0035C06D6D125410DA4B4046DCD009CA0035C07E18F25E10DA4B40B7E20242CB0035C0FBEA528411DA4B40A780D0FEED0035C0917370E213DA4B40F0AA909C120135C0FB4FC31F14DA4B40BD0DC5B6160135C0C42CAB2E14DA4B40D044A7D4170135C088CE06AC14DA4B400162F9ED1A0135C0B2F342E517DA4B4003410A21380135C0E03FF37818DA4B40435B0BEE550135C0DB8484EF17DA4B403DE2A6895D0135C097B74BFB18DA4B400310758D640135C032700DB71ADA4B4028001BDC790135C038C580021BDA4B406D48B5EE7F0135C090D0B8951BDA4B40D4ECE76E8A0135C0EE75C8071CDA4B404EB0E4C9960135C0BAF22D471CDA4B40C381D8FBA40135C02BFC5D531CDA4B40400F763DAC0135C0BAAC244A1CDA4B4046437295B40135C00E84438C1CDA4B4063D1700EB80135C0708949D01CDA4B402C1AE810C20135C088AE13E51CDA4B40D892E702C70135C006FED7A11DDA4B40F5E835ACD50135C051AE10D71DDA4B400FC71483DA0135C03E1F9E631EDA4B40E0218AEAE90135C08625924D1FDA4B4002F8C780FA0135C0765520671FDA4B40D3627A6AFC0135C075738DD51FDA4B406018522B050235C0F9B1A11620DA4B403E1BE8D9070235C0499FEAE120DA4B4093CD4FE1110235C0D4FE6D0522DA4B4059A262C1230235C01BFF885B28DA4B404DC1FFC47C0235C00DFBDA2729DA4B40201AEF6A8F0235C09F47814529DA4B4062664A52970235C05C1570E529DA4B40C9A99C0FA00235C0609920502BDA4B40DFD8F0D8B00235C02545A4BC2BDA4B40C418D071B60235C0119000BC2CDA4B40893F3C4FC50235C0425A9D1630DA4B4046973B03EE0235C03FCD0B0134DA4B403DFE10381C0335C0DD44763834DA4B40F637D5E71E0335C07FD2BF3636DA4B400D3EEDFD380335C07B45F78536DA4B40FA3BBE703D0335C01A71CCDE37DA4B40E89AB8E9520335C0B7A53F4239DA4B409A5963E15F0335C0AEAFC9C13ADA4B40CC74DF75730335C0C17CB1543BDA4B409B58D9D07F0335C0D82D99B23CDA4B407DFD58EA8B0335C0557763FD3DDA4B40A82E05349A0335C07BA2742440DA4B4076B7AAC1B90335C0AE81784040DA4B40E7D54E6CBB0335C0747429FE41DA4B4051566302D70335C06541D92842DA4B404D34624AE40335C0D50F117D43DA4B40FB2A7256F60335C0C4540F5344DA4B40EB387393FE0335C0CD32E6B846DA4B4002B5CA980D0435C0E43E9FC748DA4B4074B2D45E200435C04BF02BA44EDA4B40CD9B8911260435C05D4F20175ADA4B407B443CA8350435C00F325A7A61DA4B40F3D3A24C430435C069DB537B62DA4B4047A18437440435C0D9C1279B6EDA4B408CAAC11C540435C01E35504379DA4B4033EFC05C670435C0877A1D3182DA4B4001330E7F7D0435C06571DBAB82DA4B40C9A79CE67E0435C0BE1C235487DA4B4087CD8D368F0435C0EB7DDFE889DA4B40D743431E940435C04FC189A58ADA4B400F44EEC1950435C0E5EECD0A93DA4B40E32A7099AB0435C09B830F9599DA4B40887E0493C30435C0014DCA1D9EDA4B4065AC7C21DD0435C089F2498AA0DA4B40948959AEF70435C0714E47CCA0DA4B403D87419D120535C08D9C3CE29EDA4B400066994F2D0535C054C5D34A9DDA4B406EF2967C370535C0C8D04F1B9DDA4B40C56446F8390535C0DFDAC00D9ADA4B4050D062D84C0535C066E20AFC99DA4B40416F06634E0535C0177FA36B96DA4B40D654AA89690535C0E0E478AE94DA4B40C5DAC746710535C09EC870CA94DA4B40177CE1A5710535C03E9423C597DA4B40FFFA2FD67E0535C028C6C2C89ADA4B400B3253168E0535C0203917AF9DDA4B403A4A741C9F0535C0F6D16CF09EDA4B400E500920A70535C0DD924AD3A1DA4B40FE7AEA4ABB0535C08B08385BA5DA4B409A7092FDD30535C040AEC016A7DA4B40484B4492DE0535C0EAB06E71A7DA4B4053DF29C9E00535C0307ADD33AADA4B40B8352B74F20535C026D5F5D3AADA4B408D414722F50535C042A9871EB2DA4B403A2FEC0C0F0635C0827D86F0B3DA4B4056C115DF150635C04C54CBBCB6DA4B40CFAF76F1200635C096FBEDF4B8DA4B40BF2B63BB280635C0DDC0C75ABBDA4B409D0A12C1310635C0FED5CB58C0DA4B40C92021F4450635C007823C3BC1DA4B402A6B5AA4490635C0A63160E6C3DA4B403841D71D550635C04303C259C7DA4B40B1F9E231630635C093C8A4B2C8DA4B402EF61903670635C009C2BFCFCBDA4B40F8A4D394700635C04EC60EF4CCDA4B402EF478C6720635C02030FB47D5DA4B404B5C888C880635C06763271BDADA4B40D86E946A970635C06F65D73FE1DA4B40DE2856B79E0635C03BA75DC8E6DA4B40F3D6C416A30635C007D3180AE7DA4B40479DED4AA30635C066B8F866E8DA4B402636E860A40635C0ADA3839CEBDA4B40F0E3105CA50635C0EDF96068F1DA4B4058CFBFF9A70635C0A51EF810FCDA4B4033AA780DAE0635C07B4EB4A2FFDA4B402E57FF86B00635C0BDD6127702DB4B40A3A13CF1B10635C07BE18F7604DB4B403F5791B4B20635C03E7B7D3B09DB4B409627A1F7B40635C0CA15AFD711DB4B4000DA9B51B70635C0830BC7DF16DB4B400EA35AF4B80635C0AE5BD7381FDB4B40C475391BBC0635C0AF70182523DB4B402A2E44C0BD0635C03CC5113030DB4B404223B6C4C30635C0E2B104EE31DB4B40DB04E99AC40635C0200E625237DB4B4004E83D4CC70635C0C6FFF68437DB4B40AC57C556C70635C0D58CBC6648DB4B40ED5A7ED1CD0635C0136551F551DB4B407E1F0812D50635C08D5DC72856DB4B40E21CA2F2D60635C0ACF95F4E5EDB4B40DB212E52DB0635C0C5B4471585DB4B403FF9AAB6F30635C07398D2FE8BDB4B40730639A2F80635C012A531F497DB4B407576442A020735C06DC67C719EDB4B407440B9B0060735C0C62EE004A3DB4B40009F73230A0735C0FF3C49BBAFDB4B403569D372140735C0485625F5B9DB4B4058E675291E0735C0D71EA57BC3DB4B40B73856202A0735C0B173F711CADB4B40EE405F88330735C0D44CA502CDDB4B4067C996B0380735C05D3ACCF7D6DB4B402223B03F3E0735C093E5722BE3DB4B40377079C9480735C09154E0ACEADB4B40391D9886500735C02B566E9FF5DB4B40565516485E0735C0975FA806F8DB4B407E9FC8FE600735C075B8BD52FBDB4B4019D8EAE4640735C00D77BD9301DC4B407A50BDA16C0735C0A41DC92F07DC4B40FD5CD422740735C07B948CD00CDC4B409A77C7427C0735C09E7BBA7612DC4B40533ABD17850735C04813933B16DC4B40508976778B0735C04D19BCF21DDC4B40CFD9BC1F9A0735C092E667FB20DC4B40F1DD6B99A00735C07209627922DC4B40AECBE29BA40735C003259F0125DC4B403D44AC8BA80735C07B21488C27DC4B40DD88DBE4AC0735C03686F64234DC4B405EB0A983C70735C0654C5C1F37DC4B40ABB408E1CE0735C00E88C16239DC4B404B19DC39D40735C0E4E8F39643DC4B4061CF9801F20735C0789AC11449DC4B409EEF6A36060835C046B7DB9F4ADC4B400CE8B0290C0835C0650DB8914CDC4B40A5BF9D07140835C092F7F8B64EDC4B408610A1E91A0835C0F5227B2E58DC4B40C2B20098340835C0245935F35ADC4B40605BFC8C3C0835C0CCA309065DDC4B4088BDA8DF420835C080DF987A5FDC4B4008A349D4480835C0F97009A360DC4B40CF0B9F174B0835C022E14C5469DC4B40E47CACBA5E0835C0713113CD6ADC4B402A8D7996620835C011C02B0A6DDC4B40138D74B9680835C083F8404A70DC4B4048DE110C720835C015F0DBEF72DC4B409CC9E609750835C08BDDA17276DC4B40EBD504DD790835C005D206D57FDC4B40A4F8B299880835C09F8882BF84DC4B4012B52665910835C0B110FB2192DC4B408AEF1144AF0835C093BFCD3694DC4B4087476F1CB50835C06BDA69FA94DC4B4027C39C4AB70835C084CEE4C096DC4B408D387870BC0835C06EBDD64F9EDC4B40A650DD12D70835C072750E5CA3DC4B400578F4B9F30835C06ADD9824A5DC4B4005B2A2FB090935C08759BC43A6DC4B403E11B180120935C08D856B3BA8DC4B40B25D897F210935C00789EEE4ABDC4B40D50D6C102C0935C0811EAD7BADDC4B40754F5B52310935C0D8DB3073AEDC4B402A47004B350935C04CE3EB9CAFDC4B4027DDE519380935C056A7EB7CBBDC4B4028C24D8A3A0935C0B5BCC160C7DC4B40814F44F73F0935C0C4FCDD05C9DC4B40986892EE400935C009CF3CD3D6DC4B400EA187564B0935C006E88A96E3DC4B40054984AD590935C0BAC40BA9E4DC4B401F04DF1B5B0935C0ADFC8F2FF0DC4B409651883E6D0935C034CBF908FADC4B40F9F67494820935C004EF8FF501DD4B4063B29C939A0935C06B3C0DC207DD4B4097AFBFA0B40935C04063EC480BDD4B40FCDA5213D00935C00D9CBA740BDD4B40667D541AD20935C096A37F980CDD4B4034B15D15F20935C0B471154B0BDD4B40E0BF2176070A35C09839A34416DD4B40F80A2E36410A35C0BDEEB28D1ADD4B4046CD06AD500A35C0A51DBC5D22DD4B40431B4FA7690A35C0F85FD83226DD4B40CE7E7837770A35C05270985B2CDD4B40EFFC7A7E8F0A35C0B548D91A32DD4B40E0BEB15DAC0A35C0B497510934DD4B405C68056EB90A35C0D680F7DC34DD4B40AEFAC49ABD0A35C033D54D3E36DD4B40367BA054C60A35C0F5919EF73DDD4B40B88FBBA1DA0A35C0E566EC3745DD4B40BC7F728AF70A35C043E638E946DD4B402F47C53E000B35C07B0E6CB14ADD4B4057252E08130B35C0619C75CC4FDD4B40EC47FB03380B35C03C80A7ED4FDD4B407F8142AC390B35C0319CBD2C52DD4B40D2C8B1103B0B35C0D162C87B57DD4B409FA678193F0B35C075A6A85066DD4B4026606B574D0B35C0D6DE079673DD4B401F7A5F3F600B35C0B68F9F0777DD4B4040486D1E660B35C0010C9DA278DD4B400502F43C690B35C04AA3BE1F7ADD4B40715813A2690B35C0CCE3E38988DD4B40722CC001720B35C0BE5A900F96DD4B40BE9DC4937E0B35C02684B158A2DD4B40AD8041068F0B35C0ED3F4215ADDD4B40697317EEA20B35C0F478773DB5DD4B403A918AD8B70B35C00C0AD25FB7DD4B407D9D773ABA0B35C006AF8EFAB9DD4B40DBCE743EBD0B35C08BB1B06FC1DD4B409E75CC31C60B35C03AA42298C2DD4B40CD38579BC70B35C0910E8CA3C3DD4B405D97B5E6C80B35C00C2F77C0CFDD4B401A4F49BADA0B35C074C7F58CD4DD4B40BF90EE0EE30B35C022BC638ADBDD4B40410F178DF00B35C00EC54D1FE0DD4B407CB36A63FA0B35C045BD562FE2DD4B4061A05AB9FE0B35C0C4CE4BF0E4DD4B40EB1AF883040C35C097002B44ECDD4B40AC13040A120C35C02F5BE67CECDD4B40FB2F1073120C35C018C8BD28F1DD4B40EC2BA2201B0C35C029F672A0F5DD4B40BBFED404240C35C026E5EA4AFADD4B4012F03DF82D0C35C08C40489401DE4B401DF9FBA23F0C35C013EA6EA007DE4B40F2C1ABE1520C35C0A3D806040ADE4B40A643C3B55B0C35C09736CC460CDE4B40D845C9D7630C35C0A7A4D15F11DE4B4090E40EA0790C35C04530259614DE4B40FA7FC04D8A0C35C045EDBA9618DE4B40AFF0E4D1A50C35C07329E0F419DE4B4053A89373B30C35C09DA44C801BDE4B4021B2E98FCC0C35C0C1F6A8211BDE4B408C6E89D4E50C35C0668D54861ADE4B40C6842756F10C35C0AC13C6E717DE4B40C57C72C90C0D35C07A605D8B15DE4B40284051BB1D0D35C06F77A9B411DE4B40C2517774330D35C08E02EF8C0EDE4B40E32CCB14420D35C04D1F82E00CDE4B405459C9044C0D35C0EFF346170ADE4B4050DC71615A0D35C06FC659C308DE4B40AABCDA67600D35C0559964F303DE4B4034540213730D35C05EC4114C02DE4B40E00CAEA2780D35C089E6A99101DE4B40A0032D867B0D35C020F64D59FDDD4B409189F217900D35C0637E281AF5DD4B408C9E4B4ABA0D35C058D7A083F4DD4B4054876B7BC10D35C09C5ABFD4F2DD4B40AC8DDD6FD20D35C09F01A595F1DD4B40857EDB16DC0D35C08B405E61F1DD4B400DFBA52BDF0D35C089A92669F0DD4B4029BF3DB2EA0D35C008AA4550EFDD4B40AE539B6FF50D35C00F00CCE9EEDD4B40E7939AD4FB0D35C0C6B5EE16EEDD4B4092EC984E220E35C0B16EE7BAEDDD4B409F2EE8332C0E35C01E40160DECDD4B40257BB9ED4C0E35C012A6C17EEBDD4B40B68A6406620E35C0368DD277EBDD4B4015FFA2FF620E35C0D7A93BFEEADD4B4095076637730E35C04D13CAC0EADD4B407FF72A48910E35C0BEE5F9A7EADD4B408F3A3C16970E35C07CD3054BEADD4B406174E45EA50E35C020E51BA9EADD4B40F797ADFEB10E35C0A1A808C7EBDD4B40D1F847E8BB0E35C0388F7310EDDD4B404617422DCA0E35C0BDD972AFEEDD4B40D6732505E20E35C0DE5764CBEFDD4B40ECC14B67E90E35C03770C681F0DD4B4058641C6DEE0E35C0E0FB34EEF3DD4B4049CFC910080F35C067A7B313F6DD4B40525793CB1D0F35C058D3E040F6DD4B40724A7384200F35C0A2D6790AF9DD4B40AA57F0AC370F35C0188E6D0BF9DD4B4012759FB3370F35C014E45918FADD4B40391E68D73F0F35C04B141A42FBDD4B40F997F7E1490F35C05DB1E017FCDD4B4032C4FC304D0F35C041D36EF0FDDD4B4028E6BDFB540F35C0CF4E1D8B03DE4B402DF9D9416E0F35C080C97D5C06DE4B4090251B3C780F35C017A94B2007DE4B40207D5B007B0F35C0AAE1FA260EDE4B4067DB77F8940F35C02A0F750D14DE4B4001A5E8A6AA0F35C0792B5D1E18DE4B402F586E56B90F35C0FC352DD11CDE4B40F45FCEBBC60F35C07A73C63921DE4B4029E8227AD20F35C01EB2A07121DE4B40BBF28C0FD30F35C0B69815CB27DE4B40F023821EE40F35C0CF4C448D31DE4B40C4F8EECAFD0F35C00BCEAB8938DE4B40F98D0526101035C0DC7D9B9D40DE4B40A607BDD4211035C01CB216CD44DE4B40A727EDAD2B1035C0979BBFAC49DE4B403A825105381035C03F4FE12254DE4B40457E0D66501035C0AD3EAC4B58DE4B409B03EDDA5A1035C09DD013FD5BDE4B40D07559E3641035C06EBCE90163DE4B4013DFB966741035C0C953CE6963DE4B402882E74D751035C0C65A135368DE4B406AAA7A4E801035C04F71547B6BDE4B40F63335C7871035C071B14C096FDE4B407EFA97AB901035C06CEC3BEB76DE4B40C1F4DDF5A71035C0A17D6F177ADE4B40C5C7712CB31035C08F93D57B7BDE4B406F9ED04AB81035C0158E98057FDE4B4089086ED6C51035C0CC14E7D381DE4B403AC32AA6D11035C026CEFDD483DE4B405C7062FCDA1035C0904ADA0785DE4B40F2E561DEE01035C0885E116587DE4B407F4C261EED1035C0E8E9B9B287DE4B403A5D96B6EE1035C065C0FE5589DE4B40C1705C75F71035C0B3E47F918BDE4B40FB6B211C051135C010B8D5978CDE4B40529911690C1135C06280B5B18DDE4B4069CCA75E101135C0E3FD12CF90DE4B40F52A52781C1135C07032AC8195DE4B40A957D9292C1135C0D97E1ED398DE4B404B6A3811371135C004D256E79DDE4B40D45090644A1135C0D18AF2B8A1DE4B4049AB2F765B1135C0E9676EF5A1DE4B40C45F72455C1135C0D869B8E3A6DE4B40A228A1776A1135C03D7244CDADDE4B400435DA25811135C0572F6D87B0DE4B400ED92F71871135C010F21D14B4DE4B400E422F2C901135C024DAFC67BBDE4B400DFD7266A31135C06A5E1A5CC0DE4B4013355632AE1135C0AFBFB9D9C0DE4B403DC65A46AF1135C0C751F41FCADE4B40B36F5CD1C31135C0CA1EDB16D3DE4B40173012EBD51135C089BB44F9D7DE4B402AB08E99E01135C06A996A95E0DE4B406F064403F51135C09C5183C2E1DE4B40C403F620F71135C0CE563ABCE7DE4B40D7E00479011235C0F2D0DF79EBDE4B4065570951081235C018D78905F0DE4B40CF1ED51A111235C0EEB0A1BDF3DE4B40E7721CBA181235C03FEE673BFBDE4B409C01C301291235C0468E3A3A00DF4B40DB7793E1331235C0491DBEA609DF4B4055BC6D75441235C058405FE10BDF4B40017EAE4B481235C0558370B711DF4B4024A324BB501235C098006F3B1DDF4B402CAE93C5681235C06679176822DF4B40A37AE0C8751235C03F02BB9C29DF4B40F18533DB8A1235C0CEA756E12FDF4B40540B3863A01235C0175DF58334DF4B408DB056A6AE1235C05EB478E337DF4B407E8BF0EEB91235C07398B25E38DF4B409E94CDB0BB1235C08D67A4D93CDF4B40D78B926AC51235C052BBA0A141DF4B4033AED576D11235C07D03E59344DF4B40128DE452D91235C04243C5994CDF4B40B41F6701F01235C011AFFEA751DF4B40C412D611001335C0AAE50C1B55DF4B40D766B5790C1335C0F7B8A83956DF4B40AA516441101335C0F3715BE358DF4B40A08C65A3141335C03893C09D61DF4B40263DF71C251335C0712DB6A863DF4B403C3C3348281335C02C9E1C3E6CDF4B402C71A660371335C0C4131DC373DF4B408E0B075B481335C0955421A67CDF4B403D2B162B5F1335C026E794B17FDF4B40A51CF677671335C0F5CCEDB27FDF4B4071EDDD7B671335C088A5872D8DDF4B40E1354842811335C087B910D896DF4B40B0D10AB1931335C0B27406AE98DF4B406E2FBC4A971335C0237888C9A3DF4B4023726DB2AD1335C008FC7A78B4DF4B40B9161D08CC1335C07904D6BBBADF4B400BB50894D81335C0478EAA57C2DF4B40A9CFA961E91335C0D5926204CDDF4B406DFC5EC1001435C0151C6EE3D2DF4B40DD2633040F1435C06C03B8CCDBDF4B408E981618271435C03D1E26D6E0DF4B407E1DDF27341435C02B41F474E4DF4B401E859C293B1435C061096F87E4DF4B40A7B25B503B1435C0ECC062FDE4DF4B401B11C6F93B1435C040F75407E9DF4B405C958EE5411435C0EEFF8FD2F1DF4B40C37E2B404E1435C020083A80FBDF4B4003A7F7A85A1435C08293CEE701E04B407A5A0FBB601435C00D3323A908E04B40B1A85BCC671435C06E18D3D417E04B4007BE883B791435C00E1E0E8F19E04B4052AF20447B1435C02EFDD8F12AE04B4075F4DB39901435C046ED3EB631E04B40B9647231991435C0AA575E4C34E04B406BFF03F39C1435C0641D014434E04B40FA214F079D1435C0068870D139E04B401DA9A522A41435C04ABFCBEC3FE04B409746FED0AD1435C0F575BC0343E04B40691D49EFB21435C044557F7F54E04B40A872CD31D11435C08AFAC7F455E04B400625A371D31435C0D31D31BC5BE04B407444934BDB1435C0BF868BAF67E04B40D4F64092EE1435C099B1252372E04B4026F8D47F021535C04DDF5EB376E04B40DFF260D80B1535C0804BE84680E04B40A03DB8EB201535C01E26529582E04B407C71D832261535C03D6C81BA83E04B40AAE4B5EC281535C04C99585287E04B40F98F5A48311535C053C97E3F89E04B40469F22EB351535C08420521492E04B406B429EE94B1535C0DDF64C0A94E04B40168ABBFA501535C0DECDDAF697E04B40CB2025815B1535C01D3CCBDC99E04B40F171ACCB601535C0D40484699DE04B40BB3D91156B1535C0CAC303FFA1E04B406803A9E3791535C04F97FAE1A3E04B40824C7BB8801535C05574ED88A9E04B40D2F0DA14921535C070A7A718ACE04B401EF7EFDE981535C0A35FCE2CADE04B4046484EDB9B1535C0745513D2ADE04B40080248169D1535C000F3D14EB5E04B4085EF3323AB1535C07A4C4123B9E04B40BF56F5C2B21535C0B778F18FBFE04B409407E054C01535C08C41EE2DC2E04B403423466DC41535C06207EA5AC2E04B40896DD6B3C41535C0AAC0E05FC9E04B40CD0897BDCF1535C054EEE63ACCE04B40E4D5B36AD41535C06FDD72F7CCE04B401F192BACD51535C0235D9AE0D0E04B40E41E9936DB1535C05B49BFA2DEE04B408F9A6279F01535C0DA038787E2E04B40D37D51D5F61535C0E7670C50E9E04B40B8186F89021635C0EE0BBDE1F3E04B407D58D30B181635C054980E6DFCE04B40DE0A5985301635C01439AEC3FEE04B407D93E199381635C0E0C29EDAFFE04B40FF26919D3C1635C0AAD4F21A03E14B403AEEA827461635C06B9472C304E14B4058A89D964B1635C01A1D4CFB05E14B40398600C24E1635C0696C88640AE14B401E220A6E571635C093F2EAE011E14B4015D3AECE6A1635C0BDB7651A12E14B40F766C97A6B1635C05AD887D31AE14B40F613B54B7F1635C0032BAA0B1EE14B40C26DEE08871635C06E43F05322E14B40C55520F0911635C0C7C07B9125E14B4065C906BB9A1635C0DAB8835C29E14B400E0C1FB7A51635C0E867079B2CE14B4045FC06D5AF1635C0F886701130E14B4028E57C7DBB1635C0DDC9628B31E14B40D35C77E8BE1635C0F1EDF85B33E14B40F636D43EC31635C006979AB937E14B4040ED1205CE1635C0083F43B43AE14B40DE73BE07D51635C01A8C76683DE14B40F124AE39DA1635C0BB706D8047E14B40DD6E4230F11635C0E10F054D4BE14B40E80D097BFB1635C0FA0971B451E14B4060C1C6870F1735C0B7C3F63C53E14B4099FF3023151735C0DC9BADA753E14B402CB38776161735C0CB06578857E14B40A237961E241735C072A745C45AE14B4072F21CD0301735C0861BE5EC5EE14B40CEA2CDE9431735C0FDD7A38260E14B40FBD4619A4C1735C0311487BE60E14B402BA50ABA4D1735C0CE7F535063E14B40345683F5541735C0DDE60FC867E14B4093238DDF621735C05E3EC04C6CE14B402D45A385721735C0EC1C59CC6DE14B4041F868EE771735C0837091B471E14B40B584C4A5861735C0B029C25972E14B40D5AE4120891735C04FFA08EF72E14B40557517698B1735C0E302F76F74E14B40B13F3C05901735C089A59B7976E14B40CC941294961735C00A9ED2DC79E14B405DBF6409A21735C01EF4BCF07CE14B40D5A57263AD1735C0F2F570E27FE14B40648CC848B91735C08704F37080E14B403FCA6F93BB1735C08103405687E14B40ACE22A81D81735C0CC6C67E789E14B40C8102B57E21735C051A7C55E8BE14B40EF21AC3AE81735C0E024AE548DE14B409D8F767EF01735C00823CEA88EE14B4034A5CBBDF51735C092A0A4AA8FE14B40AA5C86DAF91735C0748B4B7D91E14B40F7259E8D011835C0C79DC39B93E14B409CD781440B1835C04AD6743695E14B409BE45F4A131835C09A2BFA4C99E14B401DD6F849231835C0DED772109CE14B404BCD6EB6311835C0C83475869FE14B40339FBAC3421835C04BDE2A5FA2E14B4032DFEF24591835C0FF4E484DA3E14B40D1E7F357631835C059239091A5E14B4072078C456A1835C0FE107642AAE14B40CEC3365A7D1835C0029A5FEFACE14B402004022E8A1835C099A0ED3CADE14B40C6741BBB8B1835C0265E9828AFE14B40513F72A4931835C0BEC0322EAFE14B40B2A948BF931835C0AF4F9A67B7E14B407D3A1B0DA91835C099421357BAE14B40029B7683B21835C075BB4CFFBAE14B40B5FA8BAAB41835C0295A66E4BCE14B405B7DB9F9BA1835C0EB263AC6C1E14B402BBA72BACD1835C09EA9E97CC3E14B40DFB35A6DD51835C0B9234859C4E14B4067DCA217D81835C05B07BBE2C9E14B4097566AF7EB1835C08B26460BCEE14B40311BE5FD001935C01B2C6ECBCEE14B40FE3F8EAC051935C0C62EB72AD0E14B40B3801F9A0B1935C0E0D1C23BD1E14B4057B39566101935C002A17510D5E14B4017CB3B5F221935C02C849B4AD5E14B40AFDB038F231935C035EFA39EDAE14B405BE14D572C1935C0152BFAA6DCE14B404C7D53CA2F1935C0A6497F09E4E14B40986230B13C1935C0FD8C5843E7E14B40F8297F9A421935C0037C3B00EAE14B40EC2380DD471935C065EBD751EEE14B40F659BE2A4E1935C06B26377DFBE14B4028B87AF8651935C0BDB697B104E24B4093C4DC66741935C0A524863808E24B40021DC2B17B1935C032CC8DFC08E24B403F45C76B7C1935C0DDDF7FE114E24B404DCF03768C1935C0F59DB22418E24B403879B09F911935C0EB45145818E24B40AD6B3AF1911935C0954562C21EE24B40932FCC269C1935C029A57AF426E24B40572B2AF2A71935C0F9168FA728E24B4067C0F6A9AA1935C0C7EAFAA72BE24B40153E7B0EAE1935C00BA8AC7C33E24B40941CD018BA1935C04486040F34E24B40CBDE75B1BA1935C0E5283D0235E24B4067DD9CB2BB1935C0CFD1A37938E24B4093F18569BF1935C0445C4A133DE24B402316D053C41935C0C7C5C6A23FE24B40CE98012AC71935C07489428E42E24B40D9C31E84CA1935C06625DBCF4AE24B40BDBE1929D51935C0266A2D1E4DE24B403563DE7BD81935C0885F91614EE24B40C487B496D91935C0A373096A54E24B4012D3FA5DDF1935C0DAD8B06F5CE24B40F6B725BEE71935C0581940BB60E24B40D0717F82EC1935C0848090A767E24B40629DC8A9F41935C0131808376CE24B40143854A9F91935C09EBCB96F70E24B400D4870DDFD1935C0C91DDC3572E24B40EB5A5EADFF1935C0C419050176E24B407152D6A6031A35C007007DDA7AE24B409AF86F87081A35C04B8D70D87AE24B4099C99C8E081A35C0A2F1C36C82E24B403EFBBBE60F1A35C07509C0A884E24B4015DD7978121A35C014CD8BC68AE24B404E9129FE181A35C0E5BE2F9592E24B40AE08104B221A35C091A34F9F98E24B405EBC664A2A1A35C0AACE2747A4E24B4027DA9D843C1A35C00471A5D2A7E24B4006DA7E08431A35C04AC0D8D3ADE24B40B9D5B90E4D1A35C04E85B969B1E24B40B62A2A54521A35C0E46085C9BEE24B404EAD7E4C6A1A35C0F48BF3E2C3E24B40E34A9E6B751A35C0CBB4753EC7E24B401396C9567D1A35C058839A96CAE24B4093728470831A35C077CB4534D2E24B40452BAC18931A35C01DEA50B7D2E24B4045ECAF48941A35C0612FC5F5D3E24B4044D0967B961A35C0F6FDC82BD7E24B40ED9CA06F9C1A35C00BE00732DDE24B40BC24B426A81A35C0B220B727DFE24B40C08C5614AC1A35C0CB6EC1FEE4E24B40EE7AAA25B81A35C091A3B722E5E24B40684E2470B81A35C0EEEC0B9EEDE24B409D8AE60ACA1A35C0C1FEDCCDF4E24B4092F63D81D81A35C0C376E87EF8E24B404AA56063E01A35C04ACE9AA8FBE24B409955228FE71A35C036E69EA501E34B400B81980DF51A35C00ADA61AC04E34B404D48F23DFC1A35C0A6D6E16009E34B40A4B4F507081B35C064982E9A0AE34B40C12F856A0A1B35C0724824C50DE34B4041715FE7101B35C0A413631810E34B4015B9C5E8151B35C0AA1CBAA914E34B403C93E00C1E1B35C048FA23881DE34B403E507898331B35C00D3720DE23E34B4060630DB4451B35C098F3682128E34B403478FE23531B35C0FD0DC41529E34B406A99A479561B35C083F71A8F30E34B40E366D41B6A1B35C025B7E24532E34B402064217D6F1B35C0B7FB3D8A34E34B408D66AC0F751B35C0FB6357523CE34B40225B7D848B1B35C0EB8B575541E34B4060703BB09C1B35C0DA937CEC45E34B400F442BD4AF1B35C03FE0B39348E34B40AF7289A8B81B35C0244A643E4DE34B402C7ACD70CA1B35C08F74605E51E34B4052FF80A8DC1B35C0E20028DD53E34B40917B748DE71B35C0523420A557E34B405F0F4775F41B35C0123090DE57E34B4006EF783AF51B35C0DCA85FF15CE34B4019F8DABF061C35C01027A01D5DE34B4070F36059071C35C0F99B534F62E34B40594ABD73191C35C0C53C45A163E34B40206483391E1C35C058E61A466DE34B4068F7796B421C35C07601B55F6DE34B40ED8DD9CB421C35C07FDEE0C770E34B40B906B7A84F1C35C0E698616F74E34B40A650E8B95B1C35C0832D4A2078E34B40BABCF739691C35C0850462B779E34B408811EAB56F1C35C0FFA63EE07AE34B40E1C7FF21731C35C0C214D6C77DE34B4019E5434D7C1C35C00FB200B780E34B405E303E3B861C35C06031ED5881E34B40EC5F5468881C35C012AF4C1385E34B400492FB72951C35C009A9CD8F8DE34B4040015641B01C35C02B576CCA8DE34B40F0ED6903B11C35C0E54AACE890E34B40AC9443D9B71C35C0D7E0E67997E34B40DA49D397CA1C35C0616E66C79AE34B400F4B1D75D51C35C0714F7E6E9FE34B4096491FEEE61C35C000BCC7DFA2E34B404933EEDBF51C35C06C601DB7A7E34B403372AD0C0A1D35C01861630FA9E34B407CFC70CE101D35C0764359DEAEE34B40E93F772A281D35C06BDE7C53AFE34B403738A1B12A1D35C07FF04BAEB1E34B40C17B7CC0321D35C0BB3D1CF3B2E34B40BB866964371D35C090192584B4E34B4070B59B263B1D35C0BA11F53DB8E34B40B54760A7421D35C012AE4064BFE34B401926E0E1521D35C02638B277C5E34B4088E5AE95641D35C073011972C7E34B40E4BC152D6B1D35C03114DDF5CAE34B40598C526C761D35C06731DE1DCFE34B4036AD8528811D35C0FE94EDE4D5E34B4094A875319A1D35C05F031E82D8E34B403EC57618A61D35C0C7DC127CD9E34B4072BF74B9AA1D35C04775C842DAE34B40C66D7E8FAE1D35C00926AF63DCE34B40BF8B899DB61D35C01E1BEB4EDFE34B4065F14AA5C61D35C0E451B961E0E34B4066BE9A13CC1D35C0AA07C5A8E0E34B409355D1EECC1D35C081B25995E6E34B4051F1E120E91D35C0B53C563EE7E34B40C74FFC46ED1D35C0DF1806C6E8E34B40FFB9EF78F41D35C06E3B172FEDE34B40B76DF643091E35C04FEBEEFDEEE34B401FA7B5F1131E35C0F36187D6F2E34B4001692BE8241E35C0BF9BB588F3E34B4060CB750F281E35C0F36DA8CFF7E34B403F9C6BFA3B1E35C01B12FFD0F8E34B40E508875D401E35C08A1CF02EF9E34B4076511556411E35C09EB470DAFCE34B4093229E284C1E35C03BE273FF04E44B4060DE08D85D1E35C0539C463C0DE44B40D20BA95F771E35C0F033E95B0FE44B401A041876811E35C0E4C2F3670FE44B40F74EFF9C811E35C010C6DE0711E44B403787A1D0871E35C031A36E8C13E44B4039000E218F1E35C02450DD5415E44B403757FBA3941E35C0505822F215E44B401179E93E951E35C0D00A7ECB17E44B40211FC1CB971E35C0A6301FB61BE44B40E22F51A49B1E35C0DF030F4F27E44B401CD60AA8AB1E35C01902587A28E44B408C384A8BAD1E35C0C9707D1133E44B40D098099BC11E35C07FE45C6533E44B403562CA76C21E35C078756AD83DE44B409192E0C5D01E35C060F118A048E44B4080A46F4EE51E35C00E03D6694EE44B40A914D5AAF41E35C081330A4351E44B40209D8454FA1E35C080FAC3DD59E44B406962DD3D121F35C07203484B60E44B403400B8732C1F35C015E12EC361E44B40A370A875361F35C0FB2D227263E44B40D8D2F2313F1F35C0A71120F863E44B40DFE13F26441F35C06765D6CA64E44B4074444586451F35C0C0ACB71E66E44B4086818AFB471F35C0C80C637168E44B405A7CE4F24A1F35C0CBA398456CE44B40BD61D35E501F35C0425388B373E44B4016AB189A561F35C00F972C2179E44B40C5C416605C1F35C0BFC6B97A7CE44B40AACE6557601F35C08AD3BAAB81E44B40612741F0631F35C0A29053AC82E44B4059E36AF2641F35C0A941EFBB8EE44B40C9965F975B1F35C0D881682E92E44B4040528B91591F35C0095D10B59CE44B404EE1789D541F35C094331A01A1E44B40331F0214531F35C0B2C6F0A2AFE44B4071542008501F35C07BDD9F98B2E44B40CA8D7248501F35C0B9D92AEBB3E44B403DCC11B44F1F35C01B277D1CB8E44B40577C07004E1F35C04F23A453BCE44B407047F9EC4B1F35C0F31B428DCFE44B40618BF057461F35C03F5FB50DD6E44B407556E1B9451F35C08DEE8008E4E44B402A33247A471F35C00D9D5C46F1E44B404F92E9433E1F35C0AF5E49CEFFE44B40B31589C9381F35C0D112449D0EE54B40C50A21A2371F35C05048A9B010E54B40DF999215381F35C05A9968B21AE54B4016BFFE49341F35C05DB4E67529E54B406F9CE917331F35C0E45AE9D829E54B407630072D331F35C0968EAB6A33E54B40AE632A3D2A1F35C01E4E963443E54B40451F8679211F35C0B3E8558645E54B40D6C81197201F35C07352E1E153E54B4075415D3B1D1F35C0A0614E5862E54B404E6F05051E1F35C0360E189370E54B400D1054EF221F35C0FF021E3D7EE54B4047A9E1DC2B1F35C07A9D47C87EE54B40B75887672C1F35C0BE6A713484E54B40E28114212E1F35C0229A76AB92E54B401CC8396A371F35C038CA3D26A0E54B408AA11EF3441F35C04ED9162DA1E54B4003F1C730461F35C063E0C480A2E54B40BB9C9EAC461F35C0E671C92CB2E54B40F97804124F1F35C0E6F90E4ABFE54B40E7765A74581F35C0F205C63CC5E54B4003DC58FD591F35C0E9E94382D2E54B40EEE4313A611F35C0D23AF0C2D4E54B40972977CC621F35C0CA481445D7E54B4063D3950D651F35C01CDC12DBD7E54B4078F5023A651F35C0BA021400E5E54B40CF4776F26A1F35C0499B6411E6E54B409EFEBAB96B1F35C07A5B31DBF0E54B408315B30B6B1F35C0884751EFF1E54B409383D7246B1F35C0D3902F4002E64B40B3DA20526F1F35C0781815EF11E64B40B0C0BEBD781F35C0682D7B8220E64B402D49B21E871F35C0EE410EA721E64B400532737F881F35C048CF5BA72CE64B4047F8B6FD971F35C001DD17962DE64B40E05930C1991F35C02718258630E64B403F7C7C4E9C1F35C0DCE4822534E64B40AC9C09F0A01F35C0741479B634E64B40C20C8841A11F35C0649222A641E64B40A414DBC1AC1F35C09ADD4F834DE64B40038BB0C9BB1F35C01585880858E64B40E0AD0501CE1F35C0552A05A958E64B4017EDE579CF1F35C03214ACA862E64B40C877C4DBD31F35C07F23C4AD6BE64B40A0C1E26ED91F35C0D571D53F72E64B40960EB101DE1F35C0D57DD3D577E64B405FADB357E21F35C0FAF281A587E64B406B089A0CF21F35C0F562A16D88E64B4029A87403F31F35C0EE784A6C92E64B403B78AC34012035C05D00BBAB97E64B40BD5B12F40A2035C015EF84199AE64B400F97D0FE0D2035C0EA2A37CAA3E64B407996DD1E1E2035C0B072FD5AA5E64B40B066E11B212035C0193997A1A6E64B40EE8609EB232035C073F3E954A8E64B40F0179F5E262035C0CE2EC039AAE64B40289A59EE292035C0C76F6CE6ABE64B408B5564222C2035C0C5425A3BB6E64B40C75B68DF3B2035C0BB5F0649BFE64B402204B02C4E2035C020B3AD76C0E64B40453FEC5A512035C071E071ADC2E64B4002F39BCD552035C0E2FDA8FEC7E64B4058E321F75D2035C0115EC119D3E64B40F4C3F587762035C041AAA289D3E64B40023E9CB2772035C08449CB8FD6E64B40E4951037812035C062AEF76CDAE64B40FF0975D9872035C0E97620F7DBE64B40E7AA51A08B2035C067C0FC67E0E64B4047EF60FA912035C0B2B05D82EAE64B4004EFFCB6A52035C03E87B295EBE64B400B0B542BA82035C02C024660EEE64B40CDED9CC5AF2035C04E6B96FFEEE64B4075DDF416B02035C03D157BACF1E64B404669ECABB02035C039547BAA00E74B4060FF1CC6B82035C04102C6BE0EE74B4064AD3263C52035C084603B8A0FE74B40E9D6D641C62035C0D50B1DAC1BE74B403C469508D62035C0353F9D5926E74B40DE2B5528E92035C0946B41502FE74B405B2A072AFF2035C0D86D37AC34E74B409DAD23BA112135C0F8897DEC38E74B4004F6610D152135C0F71A6BD339E74B40AE78E4EA152135C00FB49DFE46E74B405A455638252135C04BF731A852E74B4082550F59382135C02597358253E74B40490674FE392135C01CCF483358E74B401AF6528B442135C046EBB8BA59E74B40B04AB094452135C086A3E39B68E74B4003F564E7552135C0D6BD70C269E74B401C63CBC3572135C044E3E9DB75E74B40087D5E8B662135C068590D0B83E74B40E251749D7E2135C0604CDB1084E74B4064CF9AE2802135C0256200C784E74B405E65E6B1822135C0BCAFB5548EE74B400444CB1C922135C0C2C8ABFA8EE74B4012BACE52932135C0839BA36A98E74B4016B438E0A72135C0D0176613A0E74B40FA6CB1E2BE2135C0109712C0A1E74B404F28D034C62135C0D9541B6CA2E74B405D5AA211C82135C06F5C8348A3E74B40490CE683CB2135C0CE743FDBA5E74B4013016675D12135C083574952A6E74B401E6D74BED22135C008192AE5ACE74B402D0E71EDE72135C0376A19DAB1E74B401D2C919BFE2135C0D4FBE556B2E74B4035E9A82C022235C0B1FCDE31B7E74B409057A9331C2235C07552BDDAB9E74B40F4EB0528382235C051B01A1BBAE74B40E472878C542235C0CD0DCC28B9E74B40824D99D8602235C013F0A696B9E74B403A62DC98652235C00149D655BCE74B407F6143AE6C2235C0A3B51823C0E74B40F6BE324C722235C0AA3B7891C7E74B4047933C657B2235C049F80F46CBE74B40AC4CC792812235C0EE0F157DCDE74B4032687D27842235C0A0246BFED9E74B408A61AF8D952235C09FAA6078DCE74B406A4BC8A1992235C021E19F77E1E74B40049C2C25A32235C03CCAE03EE7E74B40E47E92E0A62235C0F5585776E9E74B40A04CA4D6A82235C0B0D63E8FF8E74B409FB17390B92235C0C9EF163500E84B40B442F51EC62235C0EE61E4B402E84B40EF9B75F9C72235C0E02BCA800DE84B4019D08A85CB2235C06075321111E84B4091925074CD2235C0A6FB0D391FE84B404CB0457ED72235C0087278B321E84B409642B1B0D92235C0971CDE2B27E84B4073490920DB2235C07AD763A936E84B400F4BC577E42235C0B02328AF38E84B40A349280FE62235C0C11CA3E138E84B40D3CB3B39E62235C0C92DDFF539E84B405AE8C46AE62235C0A5AD6A3849E84B4090559C1BEE2235C0422C4AC04CE84B40BA60F37EF02235C05F09A69356E84B400AF6283AF92235C0347D3E0C61E84B403349CBDFEC2235C0D3A5F96072E84B404F170873E02235C02A173A8C74E84B4019CC4556DF2235C0CEBC9A4B80E84B40C0B065CADA2235C08B41E64A88E84B40528393A3D92235C0B755DC1D8BE84B40B3C87645D82235C026D7B67A8DE84B40207421A1D72235C0D39544099EE84B40E85E93E2D52235C04FFB4F80AEE84B4042C1D196D92235C0089B7C5EBEE84B4049B0C2A0E22235C03F5EFB8AC0E84B40F5C1C944E42235C0D443AE86CEE84B404F821D70F12235C03E948BEAD4E84B4020453A31FA2235C097C74711E0E84B40BCE6F043FF2235C0CB3E6352EEE84B401C8235A00A2335C00E05EE65FBE84B4014A10A321A2335C00004C45CFDE84B40055159FB1C2335C06A7032B003E94B405F6DDCCA262335C0C6877C6105E94B40AEDD6AA8292335C0D622DEA811E94B40DFBB49DE422335C0A6BA47E315E94B406FCFEFDE4F2335C0A2F1470F1AE94B404F8F264F562335C0B67D240023E94B408F473AF3682335C05E25713531E94B4073518F66682335C01FEFA7ED40E94B40E99B48C86C2335C08BF2250950E94B4079796B09762335C0E4A2E8ED51E94B4065BD8F89772335C02D7DC6845FE94B40A3C13FD5842335C08AB652C96BE94B409669B411962335C0C706F34C6FE94B4010117FE59B2335C0F1BC49867AE94B40CA362003B22335C0AB16603783E94B403EB8DB6ECA2335C06C49F46786E94B40B654A6B9CB2335C0EB2FE1A988E94B40B7F5DEF6CC2335C03D15EF0193E94B40B79637E3D32335C05CA8CA1395E94B40F5815988D52335C02993DE2EA2E94B40F1B5BD4BE22335C0425A5412AEE94B40B41F60BAF22335C0603ADB76B0E94B4091B8BB44F72335C078C865DCB2E94B406CC0CD98F82335C0F7F89B0BC0E94B40DF346149042435C0720144BDCAE94B4071C7CEE2112435C0F97D206ED8E94B4026BCC9511A2435C0B57E8CE5E5E94B4000435D65272435C04877E213F2E94B40A0A0CC56382435C0755F1FA9FCE94B4033D6CDB64C2435C01DFBBE5F05EA4B4032DC8BFF632435C0BEF4D62309EA4B405FEC0C8F722435C02C8434E813EA4B403E50DCD0852435C0E0EAC37415EA4B40752BF717892435C0CBB10DD01EEA4B40B21DAE87A02435C0693168FD25EA4B40FB854A88BA2435C0D0D68B8626EA4B40BEE9E1A3BD2435C0AD87C2E52AEA4B404F66CE28CB2435C03F83C79B30EA4B409B9571A8E52435C0D0BEECFA33EA4B4015984388012535C0804BD6EC34EA4B409573760F1E2535C0F938BE8034EA4B403610B208262535C0CA2D2DFD34EA4B404292FDE42C2535C0CF643CBB34EA4B40340EA48E472535C0F601615932EA4B402A42D5D8612535C0806359E52DEA4B40A181D92B7B2535C0BC56A02E2DEA4B40FEC6E0647E2535C0E6544FD82DEA4B4057C46EF37F2535C05725F71F2FEA4B40E55FC17A832535C005EC333530EA4B406AA1C10C872535C0D61B5B8432EA4B4003E8C9478C2535C074F3DA593AEA4B403C325689A52535C0481130DF3FEA4B40CE805BDCC02535C0CE4EAE8B40EA4B408731DA41C52535C0D9A4874843EA4B4097309C06E12535C05118EF7B45EA4B40DF32A513E92535C0E8CFBD194AEA4B40056B6A6D032635C0FC9CA95A4CEA4B407CC4FC6B1D2635C066E1DEE14CEA4B401E1AC101212635C060037A334DEA4B40FBA2C204262635C0E38C8B3D4DEA4B4072724E46262635C05F9614124FEA4B4042FFD8EE412635C044127D924EEA4B40EFABBBC95D2635C00177CB504EEA4B40BB4AA710622635C0D0B59E064DEA4B405285F9966D2635C0A6B961DF4DEA4B40820623A2742635C0546C2EF250EA4B408C8D846F782635C00E3F50CB5AEA4B4079DFD0B1882635C028DDF8E95AEA4B400C66FBEB882635C0DE25989F5BEA4B40583940D2892635C0ED5DE8B366EA4B40E8DB57639D2635C00C3CDEF76FEA4B406DBA4708B42635C0ADC5D4C570EA4B40492F855DB62635C053AF29DF75EA4B407DD53462C22635C0F0A9BFF57CEA4B4029DCF780D92635C053A5E5FD7DEA4B40D6E0448BDD2635C041526C0583EA4B401CB8D494F52635C0B0E9DEB784EA4B4048CCCD44032735C0691654B487EA4B4089D11352062735C07B3B42AD89EA4B40BE12CCB5082735C0986DADEE95EA4B4006D3FC5F1A2735C0B6E81CBC97EA4B40022468711D2735C08E14AAB8A1EA4B40106B0E45312735C0E337CBF8A9EA4B40E1AF59BB472735C0F6F2AC4AB0EA4B406AA5A54C602735C0D8482488B4EA4B403ED396647A2735C0FD5BBE0AB5EA4B404EDB3FB17E2735C00BD48129B7EA4B40EA4B77909B2735C0E9E8D6CEB6EA4B4022CB20D7B32735C0A1F1828CB7EA4B4016EFE33DBF2735C055BFA6DEBDEA4B4039671ABCCA2735C0DC9087D0BEEA4B40CEE79C19CD2735C09E51360EC8EA4B4050FEF154E02735C084A5BB12D0EA4B40FBC3405CF72735C0E4997B19D6EA4B4015DF6D6F102835C0D968C143D8EA4B401C58A6311F2835C07CC4071BE4EA4B403320FA1B292835C09BEB713BEFEA4B40A9593827362835C0B2E08A5DF0EA4B40EA8641B2372835C06A428656FAEA4B405F2B8A58472835C0D060DF1203EB4B4024564369592835C06BA468680AEB4B4097E0348D6D2835C01A6F89320DEB4B40D79E8878762835C02D2B8B840DEB4B40A4AD68C0772835C0E2309F5814EB4B40FA667644822835C0CD169AA415EB4B40F65FA1F2832835C0CAFF5C9620EB4B403F338B82942835C06B8B7F1A2AEB4B40E2013CEEA72835C0670A44FB31EB4B40664308C8BD2835C05AC62C0C38EB4B403A1F8B94D52835C0ED87FC7B38EB4B40B575A0AED72835C00E4247783CEB4B40DE8741CEEF2835C0D46C5E973EEB4B409B3FF2BE082935C07BF1A0C43EEB4B40C01F04901D2935C0270D67FA3FEB4B405B0DE9A7222935C0F25C62C643EB4B40B3EFBDAA3C2935C07928DE6644EB4B4015AF37D2462935C0DC1BEC8E45EB4B40A5E3886A522935C0FC7683FC45EB4B40D41820136E2935C0D6FB941E44EB4B40C55CB985892935C0540323CD40EB4B405A5D6AF09E2935C0E49D897F42EB4B404DDD3934AD2935C01FFB822644EB4B400D36499AB42935C093F5F4AB47EB4B4048D22590CF2935C0810432E948EB4B40CD6E3639EB2935C0FA4A79D647EB4B40EE7D87E8062A35C039467F7A44EB4B40B3FCFDF0212A35C0340044EA3EEB4B408ED991A93B2A35C02D40290B3BEB4B40E315EBB9472A35C06D8234973AEB4B40C3B58D324A2A35C0554104F434EB4B40F00212C3622A35C0D540DE692DEB4B403F5AC475792A35C0AD78852424EB4B40D4ECE3C68D2A35C013BAC95919EB4B40BA1582409F2A35C0A33F39BA14EB4B4066325EAAA52A35C049C9A26E14EB4B405656D704A62A35C069093F1011EB4B40A2124F64AD2A35C0A6213EB904EB4B4002237599C02A35C0E39CC78804EB4B40CE4BE1CDC02A35C0AD79F543FDEA4B402AFCC1C9CC2A35C05594C056FCEA4B401D76F013CE2A35C0C81CAD29FBEA4B40CF41B478CF2A35C0801F9A3EFBEA4B400391C5CAD02A35C0F9D70257FBEA4B40A03431D9D32A35C0C96C744AFBEA4B4041180457E92A35C0AA1AF08DFCEA4B40F796B443F02A35C0A58BA5B6FDEA4B40F41C36F1F82A35C07861D407FEEA4B4083B234D3FB2A35C0E067E83FFFEA4B408233314A002B35C0A87786DFFFEA4B406BBF3410032B35C020D16CA204EB4B40313C6A201E2B35C0F017AE4206EB4B40A1D97B40312B35C050FEF4DD07EB4B4099DDDE50352B35C0735CC2D50EEB4B407683918D4E2B35C02AB9669113EB4B40A727508D692B35C0635EDAF115EB4B4039831F9F852B35C05B7187E715EB4B406F60FF0AA22B35C02D8EF0CC15EB4B403A065A64A42B35C0FB8A4ED512EB4B405C674422C42B35C0DAD378DB0CEB4B40ECD8ED58E22B35C01587074108EB4B404A9339DAF02B35C0F20060C406EB4B4052FD8721F62B35C0C06F6CE305EB4B400454994EFA2B35C07B0384350BEB4B40290465110C2C35C0D24BBE790EEB4B40EE43DB561D2C35C083183BA711EB4B405832F900272C35C096D0963E12EB4B401548BA27292C35C08643882018EB4B4027F96A9B432C35C08B4E937D1BEB4B400A61341D5E2C35C08892F0811BEB4B4010372E295E2C35C0F25E26A022EB4B401E7A363D752C35C0E4B362D227EB4B4078745F0E8E2C35C07587AAFA2AEB4B40B9377E0DA82C35C0E0CEC5062CEB4B40588A99A4C22C35C0A4FB65062CEB4B40CFD1DBA1C42C35C09E8051CB2AEB4B40F488F0EEE02C35C0D0A1C72C27EB4B40C39D3D81FC2C35C05695754221EB4B40E4815BA4162D35C065537C841EEB4B401A6898D11E2D35C0D1179A931EEB4B40E2A8212F1F2D35C0F97BD2E11FEB4B4086DAD715332D35C0396310CD21EB4B40D9D675743A2D35C00569838824EB4B40BE6B51DD4A2D35C02CF1141126EB4B40EE49661D512D35C033F67E4827EB4B406C0CD049562D35C0CEE64DED27EB4B4076A51326592D35C084398FF528EB4B40A6CF594B5D2D35C06609F13C29EB4B40DFC720515E2D35C0149A74A32EEB4B4015471651762D35C0D6BF031C32EB4B4049414F8A8F2D35C0ADEF8A7432EB4B40A550FD1E932D35C0C8041FDB32EB4B40FA8F58A09A2D35C013181CE339EB4B407860E521AA2D35C0BFAA41A141EB4B4048A6EAF2C12D35C0F56E7B0547EB4B40D9095C86DA2D35C08E22F58E50EB4B40844FDBB7ED2D35C0FC03F4CB51EB4B400AC287B0F02D35C0C4A13D9559EB4B409A456D87062E35C013B30B915FEB4B402F25BD481E2E35C0614A964A60EB4B407CF21BD1212E35C0ABB6A87B64EB4B4061E31A2E3C2E35C00BEDD1C264EB4B4048965106402E35C087BE0BA968EB4B407274D3DA522E35C0A334F3D66BEB4B407FC47C496F2E35C0CC1B86DD6BEB4B4073F60368702E35C0ED86559D6CEB4B402B9B89B9742E35C0BEF7F1BA6DEB4B40CCE8DFA07B2E35C0E8F130C06DEB4B40A8CCE1C37B2E35C031D2D05D6EEB4B408F56C8BA7E2E35C0D822BECF71EB4B40D9E743BF922E35C0C72284F873EB4B40437F6363A72E35C0C8AA2DF374EB4B4015DAB4F5B42E35C03156169C75EB4B4031A2B95EC12E35C042E8DFF375EB4B407A10DC5CCB2E35C09CB2DB3B75EB4B40BB3E8585E92E35C094F3503975EB4B40BBF238B8F52E35C0C7A7B31B75EB4B403B1E22ACF82E35C08D83681573EB4B405DCCF463122F35C0C7154C146FEB4B40593555472B2F35C0FE099E946EEB4B40B2A68CB72D2F35C05211130569EB4B40E18D9A1B442F35C0D04355DB61EB4B40EA60C2D5582F35C052E6DB3A59EB4B402D66627F6B2F35C0B8BF23FA57EB4B404A49C2DA6D2F35C015ACDAD94DEB4B40201ABD607E2F35C0C6ECE41749EB4B40E6F24726842F35C0A359CC0447EB4B407A31DB39882F35C0E387CAD43FEB4B406F9CA28D972F35C04F8212BE3CEB4B40ED2EECDC9C2F35C0A73EAEBD33EB4B40975AE088AA2F35C06D39B6EA29EB4B40E546E60AB62F35C04017844226EB4B4096EB7EC3B92F35C0C10C3D4925EB4B40A9A53784BA2F35C02335B4561EEB4B40F82622D7C32F35C08F9507B210EB4B4045198385D02F35C0D63E3FD70FEB4B40D19B0B04D12F35C03B9E4F090EEB4B40A28EA216D32F35C0F4E1071705EB4B40BB607DF2D92F35C0A093ADCDFDEA4B4017068B61E92F35C002C4DF2DF2EA4B400C98DD44FB2F35C060DE5064EFEA4B40FD86AB51FE2F35C06C30C899EDEA4B402F53CCB1013035C0422D35EFE8EA4B40FC88170B0A3035C06270EC8EDBEA4B40841CDFFA1D3035C0011C6D7DCCEA4B40D56A60152D3035C075257879BCEA4B407A718FB5363035C010CB95F2BAEA4B409700CA2D393035C06621D01DB9EA4B409D000EB33B3035C06DC01C3EAEEA4B40E45476A1483035C071474359ACEA4B400AB1C38E4A3035C078B2DA13A8EA4B403C203022503035C0A9CA0E459AEA4B40ECA0FD855C3035C0DAA977C297EA4B406A4DF8525E3035C021C971D486EA4B40B61B5331673035C0ED8F5F5175EA4B40FF82F8096A3035C0E80001A071EA4B4080396AFE693035C0210E4F636AEA4B40B4BF4677683035C040CF005E6AEA4B40B27F8F06693035C09E4CEADB66EA4B4092028F91683035C0BCBA3D375AEA4B40E5C5E44D653035C024015B1958EA4B40EE8DC232643035C08194EA5257EA4B404301028E643035C0CBC77FC34AEA4B4086E53515673035C03B67B42C48EA4B40F99D2547673035C0CF8FF4533DEA4B40EDFEB5ED663035C08D60BBD03CEA4B40E5E557DB663035C0526F33BB3BEA4B40472422DB663035C04917647E32EA4B40A0DD448D683035C013C2B35A31EA4B40B39D3A89683035C00C2BCDA828EA4B40531AE10D673035C07486063E25EA4B40EBCC6857673035C0AAFECEC01FEA4B40C36FB45D673035C007B1C4DA1AEA4B404D897126673035C0AA0A06D711EA4B4013EA9350663035C0B600669705EA4B40868613AC633035C093A8320AFCE94B401C453B325F3035C0077B628DF9E94B4054C28C48743035C05E4E5EC5F7E94B400D0577AA7E3035C06265FA1AF6E94B4046E1B3418D3035C0865CBDD2F6E94B40CEED8E979B3035C001A92790F6E94B4079AAA498A23035C00608A681F7E94B404B2508F4B13035C0F892127AF7E94B40DE1A5135B33035C0808C7BBCF7E94B406FB87062B73035C089D3D61CF7E94B40B58AE65AD33035C0FB500CFFF4E94B40DBBB05FBE63035C08F2865CAF4E94B40E62C0B8CEC3035C017930E14F5E94B401DA0029BF23035C0309A21ADF4E94B40196188A7013135C0265DF8CFF4E94B40594044B7033135C0D8917EA1F4E94B4045FC5C0F1C3135C01EFD066EF4E94B40E919B59D203135C0568DBEB8F3E94B40F100F024293135C051AFA1BFF3E94B4091A0E8252B3135C0891C4563F2E94B40EF7263AA423135C01A63A536F2E94B40FE847784443135C037E66500F9E94B40AAFA313E563135C0BD904B9BFFE94B406C5E27BE6E3135C0C2C6AB1E04EA4B40E555F4DB883135C06E80EA6E06EA4B408BFBDAF7A33135C02502E07D06EA4B4083D90A6CBF3135C06717F15A06EA4B4052C174E7C23135C09CDF6DF306EA4B409682D9DEC53135C0B12995A307EA4B40312A7EAECA3135C07C5C8DF108EA4B40713E3208D53135C015EE0F8109EA4B406F753B29DA3135C0C386EF5C0BEA4B40A532CAFCF63135C05D8A63B10AEA4B40DF9129FF133235C0A73F008307EA4B40D7C5DD69303235C0DC6988E701EA4B40F44F787A4B3235C065FD6829FEE94B4047009157573235C0F37965CE00EA4B407882D600703235C0A9BC471F01EA4B40ECA7FEAA803235C0EC717C4103EA4B40C15921A3903235C0A75E9D8D04EA4B40B64165CFAC3235C04BE9417303EA4B40AC921D03C93235C04A0F7A6902EA4B407D23733BD13235C034992F5302EA4B40E3240B3CE33235C075521FF4FFE94B40E8022858FC3235C0452B84F601EA4B4079561B48033335C0CF20094E07EA4B4073022BF4213335C05820929909EA4B40F264B1EE413335C036D561B209EA4B4084B63F2C463335C033C94C3D09EA4B403F9D55CB603335C08A9127AA06EA4B4012AEF2FC7A3335C0AAF9C30702EA4B407CDB482A943335C0A94DAED7FFE94B4010DFA8FF9B3335C0F976FBB3FFE94B40173D4CA09F3335C016829279FCE94B40EA80F815BA3335C0BC1A30EBFBE94B408A8CE370BD3335C08DD61835F6E94B40ADD0430CD83335C0C4AC693DF6E94B40BEFC68AFDA3335C0A1FCD078F4E94B40F55690E6F43335C0A90F77FBF3E94B40FD4BAC28F83335C092AF331EF2E94B40B6EC461C0D3435C0953130C6EDE94B4046CA7132263435C0BBD4525DEDE94B407385C7BC273435C026489E28EDE94B405EAC2C20293435C0FF3DBDD5ECE94B40C61472F22B3435C0DED80C1EECE94B4098462330303435C0B4499F1CECE94B404CB7492F303435C0C2B33293EAE94B40D6412A8C3A3435C0B0F38565E8E94B402B850BDC433435C0846DEC80E5E94B40B662FE01553435C0ACF748FEDEE94B406D3D90956D3435C04A67E766DEE94B40B69FE4706F3435C043B97CD6DCE94B40F43C6B95733435C04B8D020ADCE94B408E55812C7F3435C020336C44DAE94B40A39DE1558A3435C086653158D7E94B40F2E9F64FA13435C00DBA009CD1E94B40220EDF48BB3435C0C1AB4FC1CAE94B406DFC7F33D03435C0AF6319A5C9E94B40AE808AF2DD3435C099CB96E3C4E94B40047B4978F93435C08EAD4DD0BDE94B4077FDD42D133535C071EE4C9BB4E94B408ED588642A3535C0E3AB9C7FADE94B406982A945373535C027698B1BABE94B401B6A0841483535C07A5CC952A5E94B401B8ED622613535C083E1C3959DE94B40D5B91113783535C01090BF1294E94B40CA3F91888C3535C03DEA9B0289E94B40EB9EFF089E3535C0C74F7FA77CE94B40A414B62BAC3535C06AEA6D1572E94B40EAA42C6EB43535C0F3444D0A69E94B40D7696BB0C03535C02946DD925CE94B40CF9B75C9CC3535C03BD5921757E94B401E48664CD03535C0BADA7EC94CE94B402A3B61E7E03535C03ADBDF8E3FE94B40E18EFDFBEF3535C005DD1D3231E94B40767138D2FA3535C08C09874E2FE94B40083D72EBFB3535C0FD09E8C121E94B40EC0654CB013635C053203FE513E94B40491251E2033635C04DC288BD10E94B407F0D087D033635C01D67031F0FE94B4064837198043635C04323605D0AE94B40F92E3AB8063635C06F4DB8C307E94B40B34F01CF073635C012A243CE02E94B40EE0F9ABF093635C03707C093FFE84B40FBD757870E3635C0EDDA0D0BFEE84B40B1A7D83C173635C09FA4753FF7E84B40D4FF99F72F3635C0C85F2721F6E84B4047CF195B333635C0F65DCC4BEDE84B40904C78ED493635C0DAFF54AEE2E84B40578E8E9D5D3635C09A3F498CD6E84B40D87D1BEE6D3635C017C78757CAE84B40B6D97A64793635C080E919DEC7E84B40D34F4D2E803635C03C6A07A8C7E84B40099A4999803635C0EFE57A7EC7E84B4089F6D6A1813635C0052652D9C0E84B4005CF93889B3635C03CAED9C5BCE84B4078ABBD75A63635C09E254E2FBBE84B40FF67D465AB3635C0979362BAB6E84B40602BDD60BB3635C0913BC7CEB4E84B4089FED528C13635C0129DAAD3B0E84B403CE298FECB3635C0A52FFE44B0E84B4076949DACCD3635C022E39140ADE84B4088EBA335D63635C0390FF5B1ABE84B400EB78F59DA3635C056E0B494A1E84B400FABF2FBF03635C0DD875BB99CE84B40A46A58C3F83635C07CD835CF95E84B40F554727A093735C0A5D847D592E84B40F391AAA9113735C05AA8D30090E84B4074281330173735C05B8CA16D92E84B407050B31C263735C0B636247694E84B401BF22544433735C06CF2535F94E84B40223CB1F2473735C011A7D0DD94E84B40F0969D3D4D3735C056B3A12C96E84B400BF0EE45633735C0B1662D0296E84B404DFD8870793735C07094CD4895E84B40C53950298B3735C00577378492E84B40A5E5B343AA3735C094D2F4DB8CE84B40F9559AF5C73735C0D7B9B53689E84B405DEB59C9D63735C03D139A5283E84B40839F0A24EB3735C0BE71B3AB7CE84B407E2DAA54FC3735C0ED6346F47BE84B4080DC622E073835C0A6C641257AE84B40B8A39B2D143835C0EFA3A0C578E84B40DA74BD1D2C3835C04EFD167475E84B40AE1F0DDF423835C0B97F518076E84B406FCA214F483835C0C84CEB4979E84B40A947D3F6623835C0296B52E079E84B4057CBC71A7E3835C04319014078E84B4065BD7D18993835C070ECB27274E84B40FC2D584EB33835C003A62A8F6EE84B40A88A661FCC3835C00103AAB866E84B401D5F10F7E23835C011AC5A0460E84B407A230629F13835C0075716F25BE84B40169C15FF043935C0CC0D2FD757E84B404FE0CC3F123935C07CA9EEDC56E84B40C5B5799C163935C0132740174FE84B408BA00D2C2E3935C055E3957545E84B401D31012E433935C0F1FAED333AE84B40EDCE781F553935C040B00D3937E84B40C119C22A593935C0FA890B682EE84B40494D5951683935C09AD08E1229E84B4008019AF76E3935C0CD38A0AE28E84B408DF6F8B0713935C0D67F581422E84B4094C51AB18C3935C0D990A3711AE84B4061430FBDA13935C0707479BA19E84B4095DE547BA83935C097A985BE14E84B407612D715C23935C03E22C7BA0DE84B40C0920EF5D93935C08710AA080DE84B4063E6DFA5DB3935C0356274E407E84B40EA229AD9EC3935C02561346FFFE74B4012A5BF92003A35C0558965B5FCE74B40AF5938D0083A35C01FE5E9A1F4E74B40813D3C821A3A35C0CA41E819EEE74B40975EB81F2F3A35C05207B80DEBE74B40D5ABFAB63D3A35C06B655811E8E74B40299C6586483A35C08EA2C207E7E74B40DD1B202D4C3A35C058A4C8A2E4E74B4018093E5F543A35C092CC76F6DDE74B40F8BAF307683A35C0BF976A3DD9E74B40D1BCD195723A35C03DAA4A9BD8E74B40A6A6F4137A3A35C0EE284E1AD7E74B40C5DA60BC843A35C0A0B6339FD5E74B40025D00709B3A35C084CB236CD5E74B4072CFD1269D3A35C0991BB0F6D1E74B4011552DA4B33A35C0FF1D3ABDCFE74B40F550552BBD3A35C0B53B2C2FCFE74B40E8F8D254C13A35C06C0F61F0CEE74B4083AE390AC43A35C07D59BE1CD0E74B40F6A1022BCE3A35C01BCEC006D1E74B406EC6ED8FE83A35C0937A0BD7CFE74B406A9316EB023B35C032905994CCE74B409A4ADAA61C3B35C0160381D4CBE74B40D7197D25203B35C0FE7F9FC9CBE74B40C9CE2EB0263B35C0C9266221CBE74B40AA5769C52C3B35C0DFB83221CBE74B401E17FA512D3B35C06585C6DACAE74B40401FA815363B35C0A0F1CC04C8E74B40036FFAFD573B35C0F4E34B35C6E74B40F4B19F51613B35C0F8DA65E2C5E74B406F0F9B72693B35C0DD591211C5E74B40818699F2763B35C0AEC0CA30C5E74B40409D2CC0783B35C09BAA4137C5E74B40F1562676793B35C03796BC47C5E74B406767BC757A3B35C09D139670C5E74B40913CFAC37F3B35C0A2275CF7C5E74B40E7EED7958E3B35C083AB3BB4C5E74B40339BA33C973B35C0DB134D89CEE74B40B7F70A3CAD3B35C061C8CF96D5E74B40F836254AC63B35C05925776DDAE74B405BA0F820E13B35C0B3A9BDEDDCE74B40E27CB211FD3B35C070605707DDE74B4094765366193C35C0077CAE82DCE74B40FE11C4B21F3C35C09BB4B226DEE74B4063BA7F922B3C35C047F5749BDFE74B402D3A1977463C35C05D4944A7DFE74B4090F766EC493C35C04B14D818DFE74B40320F0917623C35C0814170CBDCE74B402A5701E4793C35C03ABEFAC9D8E74B40A8D74BE2903C35C016C08AF0D7E74B401AE5AED3943C35C0411FFF28D5E74B40BCA76B459F3C35C0095C2641D5E74B40F0F2B10CA03C35C0E77F2344D6E74B4056D8C94EBB3C35C0F8D2273CD6E74B408CF63B16C33C35C0B912F74CD4E74B40F9949E7CE53C35C05BBC9A3ED0E74B4032D13110FE3C35C0748176C6CFE74B404A9BA22F0C3D35C02AD5BFD2CFE74B409C83EB0A133D35C0BEC9ED28CEE74B406868BB982B3D35C0A7186A0ECCE74B408E1731743F3D35C0DC4C55F4C8E74B40B372A354553D35C0ADD9CB64C4E74B405A2F364A6A3D35C08A40F433C4E74B4081683D076B3D35C0E4608AACC3E74B40C15798786E3D35C0992BEB67BEE74B40CB47F4BE883D35C0D06AB8E5B9E74B40A5D373C39A3D35C004FFCE8BB8E74B4091C266F19F3D35C0E1AA160BB4E74B408BA31984B03D35C0BDCC0E89ABE74B4092B5942BCA3D35C00D6A513FA8E74B4046123F1BD13D35C02017C7D7A7E74B40AD17D1ABD33D35C0078524F8A1E74B402065C361EB3D35C06E5C914D9AE74B406A5D1138013E35C0F627CC9297E74B40161DE1DE073E35C051A5E50797E74B4055821C2E093E35C055594E6095E74B407B8FA4220D3E35C018D040498DE74B40ADD850541D3E35C09DB2483B8CE74B40216838C1203E35C0E6BDEBBA8AE74B40C81F5376253E35C07D5CB15C89E74B401410E09A293E35C07E8345BF88E74B40B75822712B3E35C0A9269E4887E74B407B044BC12F3E35C0530D9FAB82E74B40996E771A3B3E35C040F6E1C482E74B405DC26A6A443E35C0B3BA660681E74B404D5D34285E3E35C0973D1BBB7FE74B40E9214BD6663E35C03E65BB547EE74B407BA43261783E35C023145CED79E74B40A31B2EB3923E35C04601CDCD74E74B40D72C631DA63E35C0EE78758973E74B4005A52B7AAB3E35C05B469BDA6BE74B4068418D95C53E35C001AE01DB6BE74B409DFFBEA6C53E35C0A496AF6B6AE74B40AC139796DF3E35C0EECA61116AE74B40EB161B4AE33E35C02182543769E74B404C760257E93E35C024A42EEE69E74B40DE747879F13E35C0A33AFBEE69E74B40A65B1A630E3F35C026D0476F67E74B409E0EE8EA2A3F35C062BB762A66E74B401F6DFEF5313F35C0324869D565E74B40769961D13A3F35C0763528D962E74B401AD4FDCB533F35C09FF6C22A61E74B405D75B8A0643F35C0A30A424B60E74B40EED14E2C693F35C0E860BEFB60E74B40B3044122783F35C0A9902BFF60E74B40AF4A0D707D3F35C0C143297A61E74B403AEA8981833F35C05871CAEF63E74B404D05490D983F35C0D847C81364E74B407FE58DE59C3F35C0F8BACBA664E74B400261FBD5A13F35C05BF5EB6065E74B40D8D393D2BF3F35C02033E85165E74B40538DC7A0C23F35C01869736F63E74B40C8A1EF96DF3F35C06B947C1B62E74B40DFC0A00FE83F35C0F23CAB7461E74B40DDAB76B8F83F35C098EB85F960E74B402C2A9A0C004035C04BBFFDDA60E74B40C9A78E05004035C032AD487860E74B4019CDBC74054035C0CDFC620962E74B40A31D5D50204035C0F980643E61E74B408F326DD73C4035C0263090055EE74B40C9CA02C3584035C0520BBCD75DE74B40152EE29D594035C0AA43F9565DE74B403EC036F15D4035C0978CE1B75DE74B40AEAFC01F6B4035C0A37D73435CE74B40AF25DA49864035C03C65F89A58E74B40A10BC7B6A04035C02DCA8FD452E74B400330AFC6B94035C0D39DC4394BE74B400FA6ED6ED04035C0F0724DD24AE74B40C039F718D34035C0ACC5A9C14AE74B40C399A15AD34035C0C50885E148E74B40F2C2E5B6E54035C0569B41F446E74B40E09EEE9DF14035C08BBEF12444E74B40F33EED85004135C0425133CF42E74B40C08DE3680B4135C0931D770642E74B40B5048B0D104135C057A28A6441E74B40F262BCDD154135C019B69E2441E74B40BCD3A17D174135C0D6C2119E3BE74B40D00C99C7324135C0508F50C833E74B401009D4004C4135C0EC7D1B6131E74B40DDAAFC70514135C04ACB8E982FE74B401BEF85C4574135C033C72B7A2EE74B402C03D0A15A4135C05BA8F8002FE74B404B21BB6D5D4135C0D55E128030E74B4027A2CB7A684135C042257CEF32E74B40C22F835E824135C0BC4D424E33E74B40D7A127A69C4135C0C731509A31E74B4088E853BEB64135C06BD631DD2DE74B4034E6AC14D04135C001078BBD2DE74B40498B55BAD04135C0306CD1162DE74B409ED8162CD74135C0F7ED420229E74B405D5C3EF9F24135C06792319522E74B40E293B52B0D4235C01B7BF8FA19E74B401CDAB712254235C07493A26D0FE74B407B30FC0C3A4235C069F9623403E74B40C0A4F58C4B4235C0E1600527FDE64B40B113F498514235C0A4A63B71FCE64B40763DCD50574235C0E4D046C3FCE64B40247AC070574235C0798F90F6FBE64B40847E71A85E4235C0D4657F91FAE64B402BABFA84694235C012990D8EF9E64B40A4523E72704235C0F4FFCE37F9E64B40E96D3635794235C0DEFDA2BFF8E64B405B9C55317F4235C09415DCC6F5E64B400473884A984235C05481816CF5E64B40178212089A4235C076A1F58BF5E64B405DB026B69A4235C04E1AEE69F7E64B40BE8D02D4A74235C036269034FAE64B40A869FE14C54235C0C4B24538FAE64B40EFB052F0C74235C053A9955EFAE64B40527DE7F0C84235C0DD7B0804FCE64B40CF147F21E74235C0144CC1F0FBE64B403F2FFF2AE94235C0D9101925FCE64B405AB542DEEA4235C05A35D4E7FCE64B40B1A9F1100A4335C0F9407A84FCE64B40C0D95C9D0F4335C06E518FB7FCE64B404D4CC145114335C0609766EFFCE64B40E3E87EEF134335C0E97A4F5CFDE64B405109BE6E164335C081289D25FEE64B406AC93C491B4335C0F8ADA23BFEE64B40B51A7CD81B4335C016F3D8D4FFE64B4091A212AA224335C006C2C4EAFFE64B40CA2A380D234335C04DFE124302E74B40E0C61E0E2B4335C022870C0A07E74B40581ED130454335C0DACBC4C607E74B40681AAA8F484335C05799505E0BE74B40B5F9A0C9524335C0168D4B330CE74B40E6D4F791554335C0B6CB733711E74B40BB2132326A4335C0BB2DB08411E74B4040B5FF166B4335C02DB048E817E74B40F9D3CC63814335C0841C628A1CE74B40CEAC872A994335C0F28A6C611DE74B4008EE4EA4A04335C06E3C459A23E74B4048A9E718B84335C00496961528E74B40F8F36603D34335C072C9CDFC29E74B40142C37AEEB4335C0D2A690372AE74B400BDC821FED4335C0377367C72AE74B40BB3D1854F14335C0AB6C2F732BE74B402A039712F64335C0A40E4F402EE74B40CCED5A0D144435C007BC48422EE74B40145FABEC1B4435C0ECBABEDB2EE74B40864506AD214435C02C892BBC2EE74B40B64A97B8214435C0E8028FCF2FE74B40125FC3C2294435C03E74841F30E74B40548F03F1314435C06F196AEA30E74B40DC26A8D13A4435C074B9714731E74B405CC84C3F404435C06848310132E74B4006DA0A2C594435C094DF68E831E74B40429C83A6644435C013453DBF31E74B403B67828F6C4435C0FD5ABC9431E74B40293283B5714435C0A6106DB32FE74B40DCF35CC38B4435C0D4C65C3E2FE74B408C8360D08F4435C0FBB4EAD52DE74B40AD580A519F4435C048D53F772DE74B40AEDD2495A14435C08C99627F2EE74B4021CF1152A64435C0493586E72EE74B401C48477AA84435C0E778011B2FE74B401CA82C02A94435C01DCFA21436E74B40288C4E49C44435C0D1F73F7C3AE74B40F34C3963E14435C08C3F9CBD3AE74B40B9C42BDCE34435C0C6AF5E663CE74B4078C8DFE7FE4435C07937961C3CE74B40B6388AC70C4535C05B9143FD3CE74B403585A290154535C079020F4B3DE74B4015ABBFDF1A4535C045151E673DE74B40576E7083224535C0364E84773DE74B40516A1451234535C0F084D4113EE74B4099A7B46A274535C050D6FBEE3EE74B40B5E9B3BE2D4535C0C9A8D9663FE74B403FB93075314535C0F8CAB0EB40E74B402201BABC434535C016123A9441E74B405B63C56E474535C0394D0EFC41E74B405B969BB64B4535C04D7D21BA44E74B40907325B9584535C0749873FC47E74B40C264AABC744535C05AAA58CC48E74B40B3EF675E914535C0EC292AB648E74B40476630DC924535C0DD97179B49E74B4009541789A04535C094D8ABE648E74B4018540D13BE4535C085CC509648E74B40269F13D1C04535C08D32F94448E74B40F3FF683ED94535C04F2618CC45E74B400CB27A81F34535C0DBC4F17245E74B4041C97C10F64535C0C06E18EF40E74B405C15A8370F4635C06D2025503BE74B409F2DBBBF234635C0EBEA02F53AE74B4035AB8F3E254635C0639742B33AE74B40CF06C9FC254635C0FF1B52783AE74B4020C312D4264635C076ABC3DC39E74B407EF25669284635C0965902F832E74B4010F1E85A3C4635C02F83077030E74B406A78D7A7414635C0FDD5598A30E74B40E6BBE3A8474635C085C3247430E74B405B42DC6B514635C01E5E9C6130E74B40B218486B514635C0FF04105130E74B40468F4F15614635C0B11F243E2EE74B408756136D7A4635C0D55172DC2AE74B40461223298F4635C05B65A2BE2AE74B40CEA73476954635C02B9B428D29E74B401D0521FFAA4635C056997CD32AE74B402B9ECE4CAF4635C0A79844B92FE74B40E784B0B4C74635C0D0C689A932E74B406EE95D31E14635C0BE8B9A2D33E74B40BD3EF7D7EF4635C086AA914435E74B406EF48432FC4635C0807604C036E74B40BD43EC590E4735C0329B6FE93CE74B4000565476184735C031F69DC645E74B4020FCF2E12B4735C029D32D3649E74B40759A559C344735C026AD5DB251E74B40CEDA998E4E4735C00864BDB157E74B4006E483D66A4735C0BE5703B758E74B40CBD4BFF4734735C0B124DD9E5BE74B40EE3186BC814735C0842215425EE74B402BF62552914735C024E995BA61E74B40C9922760AE4735C0C65A8EBD64E74B40C246049DC14735C04BC8A4A766E74B405AD50653DC4735C0FD55547F66E74B4029AF436BEC4735C0E2770F316CE74B4092EF185DFC4735C07CE3696D72E74B408C8ED0A8154835C0E53DC97976E74B409498DF78304835C02470460A77E74B407545DB56394835C07B0B32FE7BE74B40A937614E474835C07CBBEB3484E74B40E2EFF3CD634835C003FFD58589E74B4059870CA0824835C0BF4DBA008EE74B4086C624EFA74835C03492CF2A8EE74B40A0E3600BAA4835C02036C4B092E74B40F99004F6B94835C0318FDB1695E74B402B292075C44835C0FCD699AB9CE74B40A7A1E74DD44835C0805AD051A0E74B40148E29A0DE4835C01677C9B4A1E74B40FE85144EE14835C0C1838A0EA3E74B4094FF0CD8E44835C04A5DF8ABA8E74B40B7E99478EC4835C0E17563BCB3E74B40F2101390014935C0B780248DB5E74B40CD95E2A7054935C0D9A38DD9B5E74B40F56D0655064935C0D2023101B9E74B4011DA4B840D4935C09631DA0AC2E74B40FC705108264935C0F7EC2CC4C8E74B40B30C1609414935C04A871FB5CAE74B40A568B73F4E4935C0126D6FE9CFE74B40D5E53E83684935C039681777D2E74B401C5BCC36864935C0104A5274D4E74B40A3C78427964935C03D46A89ED5E74B4031DAA1B2AF4935C03871A5CFD4E74B406DEA214AC94935C0A86FC435D4E74B402DC6FCBECE4935C035236274D3E74B406B01DE6EDF4935C088730300D0E74B40C435A205FA4935C078056C18CBE74B4019B4C230104A35C0F033C134CAE74B404530344A1C4A35C02D76AAEEC6E74B40559F6E572F4A35C0F6B903E7C6E74B404249BCCC2F4A35C0D3E121E5C4E74B40D90747D03C4A35C025FA9154C3E74B4037D3441B524A35C0D3B9B778BFE74B4041F0EDFC6A4A35C01FA2E2B9BCE74B403D30A23D764A35C0D31A936FBBE74B40C548D34F804A35C08309BE58B7E74B4085E192C1944A35C08592B088B5E74B4056FF4E5C9C4A35C0D2BD6165B4E74B40B67E4EC1A34A35C0C2B9B16DB2E74B40E6EA272AAD4A35C07592B741B2E74B40E3917A54AF4A35C0B3D08975B1E74B4044C04202B64A35C05A1D0EE7ACE74B400F41B27DD14A35C07C4FAF09A6E74B40F689A73BEB4A35C0EEF31D98A3E74B406C2D5ECBF24A35C0C5FEBE849AE74B40F82E634D0A4B35C0D00787E497E74B40C349A70C104B35C05EB412508EE74B40AEAA3BDC214B35C0125AC9998AE74B404FC14E212B4B35C0D435238D83E74B40222FD821384B35C0329AE9A67EE74B40F7151341434B35C0F278C70676E74B40B8F8A561514B35C0942BDAB179E74B400A5D61FB614B35C02E63AC487AE74B40B47BC57E654B35C02C0F654B7EE74B40CFB273828B4B35C00C2EC5757EE74B4089C38E1C8F4B35C00450FE947EE74B408E310CA8AA4B35C0A91C1E6E7CE74B408C37C7E7C54B35C06691630E78E74B40BC4AF433E04B35C04A3A59F175E74B4047ACF33FE84B35C023962EDE7AE74B402200A49DFF4B35C0977E30147EE74B40AEE63A331A4C35C0C876ED147FE74B40AA3F6D64354C35C0E74E58DA7DE74B404B7B478D504C35C02E91D86B7AE74B402801080A6B4C35C074B41B2676E74B4086C4376B7E4C35C003FB8B1176E74B40DB402765804C35C0D7FEDBCB72E74B40E32D27C29A4C35C0A260BA6C6DE74B40AEA731E2B34C35C0B16A161466E74B40E5DEE22FCB4C35C0E385EF1965E74B40F276CCCECD4C35C0EA6FC8CE64E74B40B3FBBD79CE4C35C0570F45F165E74B40A1A563C0D64C35C07C89915367E74B40EF279508F34C35C0637A9B4966E74B40595E0E5E0F4D35C0E97D2EDA62E74B401F2456072B4D35C0D741F5FB61E74B40CDDD25002F4D35C0E8F554C363E74B4005048DF43A4D35C00FE7B46F65E74B40C6F536DD574D35C0A378099264E74B408120B3E5744D35C0B40681F261E74B40E1E2CBE88A4D35C01D35BAF163E74B40103D803B904D35C083A365F86AE74B40D79CF85FAC4D35C08FF7A9456FE74B4068D5AF61CA4D35C0714EB45D6FE74B40D35CA760CB4D35C05CBB84CC70E74B40D058963BED4D35C01FB970C46EE74B40B139EEFA0E4E35C094E470906EE74B40B23776C8104E35C03C5FD6D46AE74B40522DF109284E35C0E84672D26BE74B40C373AA802E4E35C07AC055666DE74B407E3C722D374E35C0A2E0C7EF6FE74B4094A910A9524E35C033B4882570E74B400ABF7C8C6E4E35C0699143066EE74B40310EA6278A4E35C0D7BB68C769E74B40F801E3D9A34E35C0626825D569E74B401CCC1B6EA54E35C0664EC85968E74B4041909FDBC14E35C0DA74DE7464E74B408E4F0D79DD4E35C090472F405EE74B4009498D8FF74E35C00D5DF4AE59E74B404310809D044F35C0444F4CB758E74B40F5F49EFF084F35C05D94AD4058E74B403D37055F0B4F35C0A403DB9D57E74B40F5F922FC0D4F35C0FF989C9D57E74B4077A0EDFB0D4F35C0B9663B7D57E74B406B8CA38E0E4F35C044D1565B57E74B409B009268154F35C06D0767BF54E74B4040CAFD742F4F35C00F2C2B2054E74B40A8B572CE324F35C07CDFCA3B56E74B40C727811B3A4F35C0CA7F2E2C5BE74B4091730258544F35C0307569DD5DE74B40D57C2AB46F4F35C07CD731125EE74B4067192F68734F35C082C5D3575EE74B40EE712077924F35C0C7FD41B95BE74B4098293423B14F35C025D306475BE74B40A871E276B44F35C03B04562558E74B40713A7209C64F35C05BF2B63959E74B4058178E9ECC4F35C0D54147525BE74B4025A3A885E74F35C001E5AA335BE74B40DD4D8DB5025035C0A2D09CD95AE74B40C898ADC1065035C0C02FF10E5CE74B400341FD3B0B5035C009E8008860E74B40E05C4ED7245035C0D87DC9E262E74B408E5CCE6B3F5035C07FF24D0763E74B4054AE436E425035C0A8027A2763E74B40CC4127EF5E5035C0A38396AB62E74B40F78DD2D6645035C000798FB564E74B40FB04F1606A5035C01F12CD1D6BE74B4043B68B66835035C072B28B5D6FE74B40D672D3FC9D5035C00F853A5A71E74B403A4E957DB95035C019656C0771E74B40D13DE43CD55035C04E5527E86FE74B401FBBD0E8E05035C006B0CA4C71E74B40071B66FAE35035C097DDD3BE78E74B40E5C8776FFA5035C02F4D5B547EE74B402E751AB7125135C042D204E27EE74B404B0D05B8155135C09C98CBB082E74B40F1E2A777315135C057E3671184E74B40E57D98004E5135C08F2DABFA82E74B40EA0FBD946A5135C09708D5737FE74B4075BCB075865135C044F7FAF77EE74B40D30790A3885135C0D20127E47FE74B405E651C43975135C047F429F87FE74B4063F6C1609B5135C08570414D7FE74B40BBF33BA9B75135C0B4799E3E7CE74B403A3BD562D35135C0D68C22E076E74B40555136D9ED5135C0A9C34D6E71E74B406071918CFF5135C0D17603C770E74B40A68B461F035235C099AA56926FE74B407A7AB756085235C0AE0D163B6FE74B409DBE0E91095235C048AE833B6FE74B4047BB7C400A5235C0A475CD6970E74B408E7C50A8225235C022684D2A70E74B401C311F15295235C06308873A71E74B40E70FEC102F5235C0F02E3C1D72E74B40A77581033A5235C0A047D6C272E74B408F5F12AB3F5235C0F50E4FBA74E74B40009382485B5235C0395762EC74E74B400A9E3C0B625235C0ACC386B376E74B4018B169436A5235C02259051C77E74B407029C75F6D5235C089445AA077E74B40D36B75816F5235C0E0E58DA47BE74B404A98B15B8B5235C03059E1517CE74B409432E8D6975235C0BBAF5B077FE74B40AB18F3AEA65235C0E7577F9581E74B40F352DF99C25235C0F30155AF81E74B4030CEA21CD55235C0D2393EAA83E74B4019C0E934F05235C01C597BAB83E74B4034A305A6F05235C027F7ECCB84E74B40AA1232D5FF5235C0BEACEFC384E74B402C6394A7045335C0F541BAAD86E74B4040619F22125335C02292082088E74B406AD19E25305335C0C8679EE487E74B40BE450A96355335C0B261DD1E88E74B40D217F96D3B5335C042A75C1D88E74B4000AFA0CC405335C0556650F687E74B40E69270F5445335C0A47F984D89E74B404958B9714D5335C0961D0BED89E74B40416E7116505335C072AE89D08BE74B40E730016C5A5335C0ADCB81AC8DE74B40C707DBE1675335C0C9AB127F90E74B405FADC785775335C05D47EF8691E74B4024FBA674805335C0C29D7F5295E74B40CD65635E8C5335C07B3D2A2C9AE74B406F3A596F9D5335C058CB6AB29DE74B404435053DAB5335C02D62BEE69DE74B408FA8E622AC5335C001BFBCE1A2E74B409D2FD19BBA5335C0248425C8A3E74B407B819646BD5335C0B1745280A8E74B4048E32C8ACB5335C007563CCFA9E74B40394D589DCF5335C0D5DE1B3EABE74B40EE8A7B37D45335C0BF31596FABE74B402FFD71BAD45335C077E6F4A3ADE74B40DBF45814DC5335C090CCD343AFE74B40D6A48A99DE5335C06ACB7836B9E74B4039EC9FDBF35335C0B6C5023CC1E74B40109C09CE0B5435C09873119CC3E74B4061A72A4C165435C0079FFE1FC9E74B40D9C49F79265435C0C341B1A3C9E74B40C3F8019D285435C069FF9300CCE74B4063B8236C2C5435C0996642BBCEE74B40DD022B40315435C0F23F376BD8E74B405DAAB034455435C04E29B361E0E74B403B5D1AAE5B5435C02C44AE6FE6E74B407639AD27745435C0AB836571EAE74B40BF52DD108E5435C02A351248EBE74B40C9CCB795955435C0474484C1ECE74B407FB03D29A75435C0A4F70E35EDE74B403777E11FAF5435C0A2673B86EDE74B40F5E601C8CD5435C0E40AD705EBE74B4090A25615EC5435C0CF83F6C6E5E74B40B1B3A920095535C0BACC9EF1DDE74B40E39F5D0C245535C051F93404DBE74B401AC5B5512C5535C051EF606DD7E74B402A2B4271355535C07F3B86D8D3E74B40F0C691ED4E5535C01D4E1055CEE74B4086C2616E665535C0F7AA4E64CDE74B40CBC7865E6C5535C09305845DCDE74B405670A0256D5535C0DE26E15ACDE74B404BA23B3D6D5535C04324756ACDE74B40EEA8FE756E5535C0E30E578ECCE74B403736717D895535C0035D7483C9E74B402DF0C6F5A35535C00463E65BC4E74B40F3D37F41BD5535C02B269351C3E74B40057EAB62C15535C0F029DDB8C1E74B40196C3C6DC65535C056D1E661C1E74B4006EBF0A3C85535C0F42B21EBC0E74B4082DD7CF1CA5535C001CCF3EEBDE74B4036D050EBE55535C08CFD1434BDE74B402AEF1D70E95535C09D112AFDBEE74B40EB9578EC075635C092D192CFBDE74B40C81D2695275635C0458807A5B9E74B406483DD57465635C040FF7C43B9E74B40DC3DDC5F485635C0109F8253B2E74B40620742FA645635C0A3D3579BB0E74B4043B253A6695635C0DFA734DBADE74B40ECF135E5785635C0397151FBA6E74B40BFBC4B7D915635C0409749259EE74B40DC6F8AC6A75635C0F87B98CE9CE74B4069AFD3A6AA5635C0DCBE78B994E74B409CC4D605BA5635C0DCEFEFB48BE74B40F7EC9D71C75635C0BA7830398AE74B4087F46B63C95635C06DD4CA0C7DE74B40B74E79D8D75635C0EDE63A767AE74B40B202FEB7D95635C0EACCD58A76E74B404D487D68E45635C05AFCB66D76E74B403E4BBAB7E45635C0F4ED79F873E74B4082050D64EB5635C0636B7A8573E74B40B50E1FF2EC5635C0F72E12A86AE74B4051F0CD38065735C08E84B57766E74B40114A9332105735C04CDA94405EE74B403A27D343205735C0F8127EF25BE74B4038C21D27265735C0AB531C3D55E74B405C2799BC325735C0CC95BE6E50E74B40798CA7FE495735C0672CD6AE4BE74B4098230249595735C02BAEDEAC4BE74B4032B15B66595735C0CF7BB0584BE74B4016CD91945C5735C04442E26747E74B4000F6AE4F775735C008B6DC4A41E74B40D5E62091905735C05EE9C62739E74B40B37145BBA75735C0A0B469312FE74B40D242873DBC5735C06CCAF2A523E74B408362E497CD5735C0B47EC16C1FE74B40A6EA381FD25735C012ED176C19E74B408A0A764CE05735C00DC85E2019E74B40B7D43F43E15735C056F1D5AB14E74B40E65EECB2ED5735C0A02A6B1211E74B4040CB5CC6FC5735C0014D849A10E74B406F60914DFE5735C08ECCED4E10E74B401381C372FF5735C065E9A0D607E74B4096AFBF141A5835C0E447247305E74B40C2738449205835C0EF9296B704E74B40314FAF17225835C002C4035B04E74B402AA7A154235835C0E388D148FEE64B407B5B8A00325835C01572996BFAE64B40DEC0CB114B5835C0BBF1D0AEF3E64B40408C0677655835C07F2756B9EAE64B401D98F86D7D5835C07768610EE9E64B4024296030815835C036576070DCE64B40E8C253BA985835C0F0D07E7BD3E64B4000D3D116A45835C08C7D04D6CFE64B406CC8C98CAE5835C05F2340CACEE64B40376310C7B05835C0C82E4EF5CBE64B40856234F6B95835C044C1221FCBE64B4009564240BC5835C05DC7A9A8C9E64B408B1ED21FC15835C0F889E4B1C7E64B40B17D14AAC55835C08D0BFE68C7E64B4054A7CFEAC65835C0121034FCC5E64B40D4E78729CB5835C08C0AA365C3E64B4010CD69EBD95835C039208493BFE64B409CEBB074E75835C0BCD7BC04BFE64B408614B44CEA5835C06220CA0CB8E64B4069128AF3015935C08800B2ADB6E64B40253C6CDE055935C0D13A3790B4E64B4097E80E2B0B5935C0CD4FEB83B2E64B40E761BA08175935C05AE9E7F3B0E64B400C3698711C5935C0E2EF99CFB0E64B402BDE16941D5935C012DECEEBAFE64B4015C76EB7225935C08973766CAFE64B40BA628382255935C0BBE52B72ADE64B4078EBE150305935C06B4445E2A4E64B40ACF5E1A3525935C06A76B431A1E64B40F51A33115E5935C024793C899CE64B40321FF7256B5935C0494B828A99E64B40CC7D55CD725935C0D4C2587999E64B4056FBC1B5725935C02C98296596E64B4010A875C77A5935C0DA1209798FE64B403061857D8F5935C04C1428E385E64B40C32DFE36A45935C00D9B7F6D83E64B40AABF191DA85935C0D7CF273082E64B404A04B274AB5935C0C4166CF27CE64B40EFBE1AC5B55935C0E7F7D10F7DE64B400F1244F8B55935C04D52C76B7BE64B40BAFB8F47B95935C0328648D478E64B40CB4B8CDFBD5935C0B3FE712978E64B404F7FBD2FBF5935C0ECF98C0B77E64B4068E62B12C15935C071D82F0977E64B40B902410DC15935C05F51A58672E64B404C47910BC95935C009A912196CE64B40D952EECBD15935C0E3FD82B36BE64B40F5E8A93ED55935C0436A762366E64B40502A7ED9EF5935C095D1E85E5EE64B40DDEE5070085A35C06640F7B05DE64B4022D4CEF6095A35C08D97A14B57E64B40C9F062551E5A35C05F4B42844EE64B408046B4B3325A35C077112C3C44E64B402CB95D6E445A35C07E1F90AC38E64B40BED8BF22535A35C047D4BD152CE64B40C8400E7F5E5A35C0106C5A5C2AE64B4097D7A8C45F5A35C030399BB222E64B40D7DA1736645A35C02F171B191DE64B404EF514556A5A35C02B8F88671BE64B4032807BDD6B5A35C00B6FA3051AE64B402C4333176D5A35C0EC9FB75B17E64B40792723676F5A35C05185337E0AE64B4070F7A882785A35C08623CC3D01E64B402C3289507C5A35C096820E69FDE54B40D138636B7E5A35C09A2A59B8FBE54B405CCC9B1F7F5A35C0C961A0C6EEE54B40E110A4C7825A35C0769F7701E9E54B40DF7448E7825A35C017517BD3DEE54B402E50891C8D5A35C0166E0556DDE54B4023A5CC508E5A35C07DB2EE04DCE54B401F185B248F5A35C0E104DCF4D5E54B40EBE11AB3985A35C0055B79ADD0E54B401ED6B6B49E5A35C0E652190EC8E54B40ABA8852DAB5A35C0DFE0A692C4E54B4043A091C7AE5A35C0A2CCB75FC2E54B4076749D98B85A35C02DE8E6F5B9E54B40005FE588D15A35C095E836E8B8E54B408AA5F525D45A35C03DEB2197AFE54B4081B97DFFE75A35C09CC5EACDA4E54B40101F8309F95A35C0758E08369AE54B402CF9783F055B35C05C1301BB98E54B401C9BD010095B35C0A379408F98E54B4037899C63095B35C07F396F1994E54B40865DF5C9145B35C07D541AAA88E54B40316E4390295B35C0B22ACF887BE54B40120231753A5B35C0C05D74146DE54B40510FA2FE465B35C06D9883B55DE54B401B19F9D14E5B35C03BA9D2FC5BE54B40D29A41694F5B35C06694AD8B5BE54B40B16B3B7F4F5B35C076F4963A56E54B4011B2D591545B35C023C083C94CE54B405D1160325A5B35C00D28059045E54B4003C49EA7605B35C02EE6DC1038E54B4015B05F3C675B35C0776A31392FE54B40FD9EE01A785B35C03745694B2DE54B401DAD82C97A5B35C0CE9E5C2C2DE54B40CD11BD0C7B5B35C0C61B676A2AE54B4002DB62587F5B35C0F6CE88DC29E54B405C266784805B35C025D8A55622E54B406ED23EED8B5B35C0D60A703D21E54B40F82946A38D5B35C0B8BCEF2121E54B40B45771C18D5B35C0DCB81DB21DE54B40F9F4CAF7925B35C0EADBD1A91CE54B40A44EFF48945B35C033D3564617E54B40303304FC995B35C0B37B64A812E54B40ACA335DFA65B35C030631A8209E54B408D7230D4B95B35C00AB3767C06E54B40D8F85F84BE5B35C0436B911905E54B408F0D1DE4C25B35C022288F3802E54B4034D87C9FCA5B35C00B5B2B57FEE44B4025295952D45B35C0700B26C9F8E44B40DF84AC42E15B35C08FFFBB76F1E44B403B188D6FF05B35C0AA649A28EEE44B40C9E9108AF65B35C03FFF3805E3E44B400F48BF28085C35C0014F6992D6E44B406EE8065F165C35C0DAB3871BC9E44B402B10DED6205C35C0F3A8EBA0BDE44B40B0C8C916265C35C010E12F0DBCE44B4058BCCABC295C35C0C8BE1B2AB1E44B403F52E02D3C5C35C04EAF72E3A4E44B4034F159404B5C35C0236F1334A4E44B40DD091BD54B5C35C0DA0DAC27A4E44B40096B20F54B5C35C0E6E6AA66A2E44B40DEAEE0D04F5C35C09B675D4F9FE44B406B5BF98F555C35C0500E526F9FE44B40D6688AC15C5C35C0D4B8F6DB9FE44B40BA0C1A83605C35C0E5560EACA0E44B408737F7777B5C35C03465694B9FE44B40DA572F58965C35C04E042DC29BE44B40A1E8C184B05C35C0EAAA432596E44B4053E7D462C95C35C0BDBF616294E44B40312BC0C8CF5C35C03A0AE4A58CE44B40026F2039E75C35C07CB6C31183E44B401FAB4523FC5C35C01438E9807AE44B405A645AD4095D35C07601BB7F72E44B40EE93074A195D35C0CE938F1467E44B400E3AC186295D35C0211E0B8B65E44B40024805632B5D35C0E6C8929558E44B40CEC36B84385D35C095382AB24AE44B40DF9841BA415D35C06FA8B0363CE44B40A53A94CB465D35C027B2B17C2DE44B4005540E99475D35C0398D3BDF1EE44B40F888B91D445D35C079155DDA15E44B4009A44A383F5D35C0C6A1ADD812E44B400BC165D2425D35C0C29E9A840FE44B4009043A12465D35C0729F317107E44B409CEC014D4C5D35C03CB1F9CC01E44B406E33B8565C5D35C0364764BA00E44B40EF0726F25E5D35C0D49135F1F4E34B40447543BC765D35C0A976CE0AE7E34B408B2EFD1B8A5D35C01B108C3FE5E34B4041A74B298C5D35C0223ABA49D8E34B404F802489985D35C0BE8C4478CAE34B40B1E7C00CA15D35C0CFBE2AD2C0E34B401FF4460BA45D35C0351E460EC0E34B40057F6BCDA45D35C09ACD66B8B1E34B40BCA770E7AD5D35C0D5505CCBA2E34B406190869DB25D35C04047936E9DE34B408C69BDAFB25D35C0D7AFE02897E34B40ADDA8E82B95D35C069B2306B94E34B40BD66BDB4C05D35C05B971CD389E34B407EB1EACED45D35C0E6C2E0D384E34B40FDF0ADABDB5D35C009B59F477DE34B4020892F79F15D35C0F1FE247E73E34B40FEFB3CF3055E35C02715EC2268E34B40704D465A175E35C09A3D967B5BE34B40B05A9743255E35C0D39471E253E34B4057AA3DE12A5E35C05E42790449E34B40E28A0A23385E35C0A555532946E34B402DD281FD3A5E35C06BB09B0F38E34B4053F3F06E465E35C0EBD9AD2829E34B40343E5B754D5E35C0A4DAC5D819E34B40A34780E14F5E35C0D3CC598015E34B403B99FCE74F5E35C0685B9A7714E34B407022D3E64F5E35C0D222980C0EE34B40F48BD2CE4F5E35C03EAD919A00E34B40C6D162D04D5E35C033B70B72F3E24B4008460A44485E35C058FD34D7E6E24B4008D988463F5E35C0D910E48EE2E24B40407605D43A5E35C07CB039C9D6E24B401DD121783C5E35C042BCFCAFD4E24B40C2F2841E3C5E35C0CBA89E99D4E24B40D1D3BF243C5E35C0F316BDEBCFE24B40BA10B9D8405E35C0547ED0E5C1E24B402F8380024A5E35C09E956148B3E24B40BCC921F54E5E35C04BC7436FA4E24B4013C786914F5E35C09185C1B795E24B40D1FAD8D34B5E35C05A264A7693E24B40945F8FE94A5E35C005C59FE084E24B409624DEA5425E35C0BBAADF2F81E24B40AF5FCB443F5E35C000401C767FE24B40C3365A313E5E35C0A64EBD2E7CE24B40220C14623D5E35C03288E6C375E24B40787C07F63A5E35C0741534E071E24B40D23F66AE3C5E35C07D1134DE6FE24B400B204AF53C5E35C05AB1E98F6CE24B406BF324D03F5E35C0A647620C5FE24B40BE64482F475E35C0F890D61951E24B40D7BA0ABA4A5E35C0DD55FB0643E24B40F72C6F5C4A5E35C0DBB63B2335E24B4026128618465E35C0989625632BE24B409DC70F39405E35C0226B54F227E24B405058F54E405E35C0BA28315219E24B40C4E4D15E3C5E35C0343227330BE24B40EA1C0039345E35C00C5608EDFDE14B4040D22B10285E35C09E2661D2F1E14B4038BAF32F185E35C043026EB5EEE14B40C3CE6591125E35C0E205730CEBE14B4040F96C430D5E35C0B03E3E51E1E14B4049AE5A3AFA5D35C0BCC2AFD5DFE14B4063A64B4CF65D35C046827919DFE14B40DC71B3AFF55D35C0544CA10ADDE14B40870A8498F35D35C03BDF8371D0E14B40C810DF42E45D35C027C2E048D0E14B40B79213FEE35D35C01CACC042CFE14B40FB967E03E35D35C0A25A71BFCAE14B40B5281669DD5D35C06A13A5ECC2E14B40173DD1F4D95D35C0D8FC55F7B7E14B4007B3F17DD35D35C02D93DF44B7E14B405BBCAEFCD35D35C0A6D707DFB3E14B408BA91544D55D35C057211565AEE14B400FBE2327DA5D35C0CD38373E9FE14B404A1FFE41E25D35C07D7D7A958FE14B40A078828CE55D35C053FBBA7B84E14B40DF1B1069E45D35C0D71F1AA181E14B40BD651C8DE75D35C03727699E73E14B4050DC11CDF15D35C051D156EB64E14B4066D746C5F75D35C08B589DE655E14B403149434FF95D35C04949C9AC55E14B40D376EF43F95D35C066BFAC8C4FE14B4040DBDB7EFA5D35C0E04B644040E14B40CF4AF0EAF85D35C01FBDFC4931E14B4037CB6EBEF25D35C0F38B820D23E14B40D3C39F22E85D35C07291A0EB21E14B401850D1DCE65D35C007A02E5C20E14B40B28A330BE65D35C0140FDE6613E14B40B36F3D1CDB5D35C03FF22D7607E14B40A097AEABCC5D35C0AD4DD0CEFCE04B40D3E29A0CBB5D35C01A27D5FCFBE04B40132D2837B95D35C0552C7813F5E04B400A17F082B75D35C0BB3AE854E9E04B4027F8FCADB15D35C098DDD79DE4E04B40E6DE7FB8AE5D35C05F40227DDAE04B400CDEC2DCA65D35C0F1C76771D9E04B407E11F8F5A75D35C03D0041D0D1E04B401AF283CFAE5D35C0D46BFBCACFE04B409C594F33B25D35C002F53F88C7E04B40646058EDC65D35C036846F5DC6E04B4098F19771C95D35C01B3A0CB7C5E04B40947F80A7CA5D35C0D8235B86C5E04B40AEF6DC8CCB5D35C035E7AA7FC5E04B409BA1C813CC5D35C0472CC8E4C4E04B40A7352E67D25D35C0B3CBD87DC1E04B40994E6E5FEB5D35C0B45F4932BCE04B408CF24228035E35C07473A31EB5E04B4090A27941195E35C01FCEAB19B3E04B4017927FE11D5E35C0FAF586E2B1E04B4078D575D8215E35C01F85A4FBADE04B40B1644E182C5E35C0C53A7E85A1E04B40D70934BE465E35C09876A6EFA0E04B40D95B7BC4475E35C0167405A29FE04B40AF582F3C4A5E35C06F50A65B95E04B40DC4170125B5E35C0971F1BE689E04B40E05348F8685E35C02216377E7DE04B406A53F2A3735E35C0D7DD8BDD7AE04B405513EB7B755E35C011F0BCAC75E04B402CB1FC36785E35C0BCE6CB6173E04B409C9F76CB7E5E35C0343993696AE04B4097D5BEE3915E35C0B0D0271460E04B401EB36463A25E35C0555DCC9754E04B4002F1C5F3AF5E35C0E6BAD27552E04B40B2CA5D16B25E35C06893F92F48E04B4074F77FF0BA5E35C008D6CE6C3DE04B409A31EE6FC15E35C0D332BBAB3AE04B4035B927C4C25E35C0C65093D035E04B40FB44A92CC65E35C0BFFB5D7C25E04B4013C5969BCE5E35C0C60586E11FE04B40FBA47983D05E35C043042BBA11E04B409F54DE4AD35E35C00E58CB8203E04B4073B82712D25E35C041516A8DF5DF4B40BDF261E0CC5E35C0E494B7CCF4DF4B406F0D007CCC5E35C0A61CCECCEEDF4B40A57E1519D85E35C067F01534E2DF4B409F7AE888E95E35C069B5EE81E0DF4B40C83FAD7FEB5E35C0EF4F18C2D3DF4B403C8F57DEF75E35C06FBE6A28C6DF4B4075A7557D005F35C0E7A238C9C3DF4B4009695DA0015F35C0B22589DFB5DF4B40654E2742065F35C08F0724A7B3DF4B40CE415FAE065F35C0C5303D12A1DF4B408B5A36CA065F35C0488FB4E09FDF4B400DDFA693065F35C0C199448C9EDF4B405F1FDD73065F35C00699255B8ADF4B4067F2816E005F35C0B42452E088DF4B40F2B63BADFF5E35C0307FC67588DF4B40E60887E4FF5E35C023CFB6C583DF4B406DD85EA2015F35C087DC448581DF4B4026D5006B025F35C0B73065F377DF4B40A2787486055F35C0CA580FFE65DF4B40A95A691A085F35C0454D74CF64DF4B407874D50F085F35C031EE02BC5CDF4B40166FEE14105F35C05BB567F74DDF4B40A6A66A57195F35C0DE35F51C4BDF4B40A34E2D321A5F35C025B59E5144DF4B40D34AD7A3205F35C0A256469036DF4B40C0DAB40F295F35C041E7CC4828DF4B40A4516F762D5F35C0CFDA470D1CDF4B403CC9BDB22D5F35C0D91560FB1ADF4B400F727BE42D5F35C0223161BF0DDF4B40DE2116D22C5F35C050FCC65B0CDF4B406DB166862C5F35C03AE8D18D07DF4B40D4C5FA642C5F35C085335D9E06DF4B400CB7A9612C5F35C085E8036105DF4B406BD6B88E2C5F35C09B8A2369F8DE4B40A4C37ABB2C5F35C0F60472F5F2DE4B407B7D1E1B2C5F35C056B270B2F0DE4B40522A4EA12B5F35C07F3539C1E8DE4B40B7AA5665305F35C0EBD542B2DADE4B405850E4A2345F35C07491298FD7DE4B4036EB4622355F35C0DDDFEF79CBDE4B40087B9A12355F35C0FC9A2240C2DE4B40220FA7013A5F35C08587BCEEB2DE4B404205F8533D5F35C0617DA087A3DE4B40D1C819F43B5F35C0A2E5004E8DDE4B400435418D365F35C0342DC23880DE4B406AAFCF98315F35C0E5403F8D7BDE4B40E708252D2F5F35C0E4ABF2956DDE4B40D1F88EAC255F35C0B957192265DE4B403BEC36EA1C5F35C0EB16300159DE4B40BD54FB5A1A5F35C08D2970B357DE4B403FF443AB195F35C02114725053DE4B4026B02087195F35C09950DBBB47DE4B40E2C684A7155F35C0CDD9D4383CDE4B4079CB143E1E5F35C0BA8296662DDE4B402F8A8A69245F35C0428F7C3C25DE4B4069B3354E255F35C0DBEF4BCD24DE4B40EABFC291255F35C00CB932C812DE4B406F3EAAB3295F35C078BD30530FDE4B4095E6A2DE295F35C0FFA3289506DE4B40DDB4F053385F35C0ADC3769804DE4B40BB9AA81A3B5F35C074568AE8F7DD4B40A338730F4A5F35C02F4E0F1FEADD4B400B065115555F35C0EEEB4394DBDD4B4098C9B4E55B5F35C0A5844D98D6DD4B40883EBDB55C5F35C0FDA3281DD5DD4B40462745A15D5F35C0BABDFFAED3DD4B40442835095E5F35C0ED2C0D90D1DD4B40D7C248EC605F35C0A59C2545CCDD4B40F26DC010675F35C05D949E30C9DD4B40A4260DD86A5F35C0A27FF81BBDDD4B40FCDB9B68775F35C074C0932EB0DD4B4092D9D38C805F35C0AEE5BDAEA2DD4B406646FE12865F35C0F201E1E594DD4B405F7C10DD875F35C0E15AF41E87DD4B40426D4FE1855F35C0C9A6E4A479DD4B40F18D842A805F35C03284FCC06CDD4B40EA27C3D7765F35C037E7195F68DD4B4045B5BDF6725F35C0BE34CFAC5ADD4B40B5855E1A645F35C037A2BF624FDD4B40706C9292525F35C07583245D45DD4B40A2F2582A555F35C07DC28AD641DD4B40A11997A6555F35C0F876CAAA41DD4B4038B7B6A8555F35C09298245939DD4B4018470D9E635F35C03DAD6FA12CDD4B409BB177EA725F35C0B81200C425DD4B40B67B3A86785F35C0B06929F31EDD4B4070530ED77E5F35C0B658E36B1ADD4B40B525DC6F815F35C0511F2A6417DD4B40DE589CCF895F35C00DC75C9314DD4B40A784025A905F35C09888F8A909DD4B409ADE9FC4A55F35C032413501FDDC4B40238AC27DB75F35C0088D21F3EEDC4B40A5FC5407C55F35C02F58BAE3DFDC4B4015CC0801CE5F35C0F777233ED0DC4B40D2E7032BD25F35C0DD59E3DCCDDC4B40E2DFE76CD25F35C0E29ABB95CBDC4B40BE99E82ED65F35C0BFE17BBCBFDC4B401BB50982E45F35C09A0852D2B8DC4B409E89C05BEA5F35C05C493BAEAEDC4B4072EA092DF95F35C0F59BDA36A4DC4B40A88CE5CB036035C0FB3DEA8EA3DC4B403B1C76E0046035C051F0199796DC4B40636BBB17146035C01741CB1A96DC4B40445E9A90146035C0356922C494DC4B40B870FD9B156035C08E5143B491DC4B40FAAF4F931B6035C06A0C4DB58FDC4B407732DFE61E6035C023CB0D7883DC4B4044C72E31306035C0B7FAF4E675DC4B40AE54AC8F3D6035C04E5BD05C67DC4B4065ACDCA8466035C0B868079665DC4B40A68EA97A476035C07B00051B57DC4B40DF99A2F74B6035C0B1ADC36D48DC4B40C7BCF7364C6035C05CBFF3B047DC4B40F39DF7024C6035C0252067E244DC4B40BCD3139D4E6035C021A82A6B37DC4B40A50951B3566035C0413C86F931DC4B40C25B255B586035C0606CA87629DC4B40639674BA5B6035C06611C53D23DC4B40BACE67705C6035C07D27F19E21DC4B40596181485E6035C0D0DA78D91ADC4B40D0465979636035C0A0FBA2C219DC4B40D50C531D656035C0FA31F28F13DC4B4051F1B5D96B6035C0759972570FDC4B40BF07F66D716035C08172FEF20DDC4B4090DDB786736035C002B304FE0BDC4B40ACAC58DB756035C026E0FA1709DC4B40C29533B0796035C0A0E72BBF05DC4B40FDA3DF577D6035C0576C5ABC05DC4B40BD33D14E7D6035C0ACE2E241FCDB4B40AC5F8E98886035C01DE1E448F8DB4B4020FAC63D8C6035C06A4FA996ECDB4B402F937037956035C027C1E05BE0DB4B40B0FC8B329B6035C0D00E381DDEDB4B408E2B14009C6035C0C71D3D85D8DB4B4016FFF252A06035C090527011D1DB4B40470E2CBAA46035C08149BF9DC4DB4B407EC0023DA96035C0E092676FC3DB4B40DC607CDFA96035C03E8C94F9B6DB4B40B9EC33E6AE6035C0977EBD28AFDB4B4013777709B06035C08FB7CA19A9DB4B4038B2D460B36035C0B904C1159ADB4B4024B892F4B66035C0078958AE8BDB4B40DF21320EB66035C08F29DA588ADB4B406AF574ACB66035C031DEA50D7EDB4B40724DACC8BA6035C0BC40EB9371DB4B40534FDED4BB6035C016F69D446EDB4B409B0A5BB3BB6035C0C26FB1E365DB4B40B935AC7CBB6035C080E3204063DB4B40F124D259BB6035C0CA61AA845ADB4B4021A31DACBA6035C0DF3204A154DB4B40DE4DE3D9B96035C05F417A1A52DB4B404D54DE57BA6035C083F794B74CDB4B404EA76D99BA6035C0647502E745DB4B401197B948CB6035C055408FA444DB4B40033B16DACD6035C0C24846DF39DB4B40840257A5E06035C0DF36A9AD2DDB4B402ED71817F06035C00D0AF75A20DB4B402B560DD0FB6035C0D9EA663912DB4B403FC3DD87036135C09BC22CA003DB4B40017EE90E076135C0B3065FE9F4DA4B4045F36B4F066135C09324158AE7DA4B40EB47A1AF016135C01B492A0AE4DA4B402EB00F9A056135C01FC14F13D7DA4B40ED58AF980F6135C0B6025277C9DA4B40AB1485E6156135C05A9495E0C6DA4B403FD498B9166135C07A2BC9B9B6DA4B406F8DA43C196135C0ADD6EF94A6DA4B407E320B8F166135C0A52A68049DDA4B40FB9968CD116135C0313C65569BDA4B40F6950E0D126135C041059A7A97DA4B404768D7FD116135C053437C8486DA4B40607C43DC0E6135C078329D7083DA4B40F294AB360D6135C07266A81F83DA4B4046B0FA6C0D6135C0924542C174DA4B4060CA0096126135C03B48282266DA4B40E9971E8B136135C0FE747E9B57DA4B40DFDB7D46106135C07C19AD4853DA4B40360FBCFF0D6135C0AD1A6F0D44DA4B400AB11F71106135C05220E6DA34DA4B40E70BA6190E6135C0A0DBFFA42DDA4B40756281F3126135C0E0B3FE3020DA4B40DB84850E186135C0F52A43571DDA4B402E0EAABD186135C0FB69E8061ADA4B4037738501196135C0D7228A9115DA4B4065A168231D6135C0D652886704DA4B404968BC14266135C0F146B2C3FFD94B40991828A3276135C080DDA8B4F1D94B40E99320592A6135C005798A96E3D94B4054E7261D296135C00DFFA7B9D5D94B4000C940F6236135C0D49FAC20D5D94B40B826CDA6236135C0B95F605FCAD94B404EDDBD7C256135C0FA78AA13BBD94B40F0E1A772236135C0C7E21B28ACD94B400688DED01C6135C087EFDC23A7D94B40FDDF2EE6186135C0E75199729DD94B4039287080166135C07330D8C88FD94B407E89151A0F6135C0E93A176E87D94B40A17BDFE4076135C0351D3EA77DD94B40679D5D88016135C0CBF6AE9E70D94B406C243080F46035C0635653D364D94B406E057DD5E36035C0F4BEA54C60D94B4081C37210DB6035C0BBA7D60D5DD94B40E48E89F1D96035C0B072E56057D94B40CF92EFFBD66035C01B0F51DE52D94B40C9CCF731D46035C0D620BE3D50D94B40AA03D012D36035C0931C9FC84AD94B40EB4F1100D46035C0242756B143D94B40FC91F010D36035C0680ADE6439D94B40D682E07CD26035C0C520CF8D37D94B40F61F4993D26035C0BBBFB6A82BD94B40954E2AA7CF6035C09F2966F226D94B40D9AB3381D36035C0A508395917D94B40BE9EE1B5DA6035C098D0572112D94B4073E9E541DC6035C017CC8B470DD94B407829B974DD6035C0321AE9E509D94B40C20F2A20DE6035C03E3AD0FAFAD84B4033C31FDEDE6035C06286FB9BF5D84B402176B98ADD6035C028ED9C69EAD84B40586F6529E56035C0B3D88216DED84B407B6CE535EA6035C064DC3D69D9D84B403968E785EB6035C0B7AA7866CCD84B403086FA78ED6035C032EFAC5EBFD84B407D64590EEC6035C03F4B1591B2D84B40EDB9E34CE76035C0033F3686AED84B408689883FE56035C0DB0AE861A8D84B40106C0E59E16035C0FC2F5720A6D84B403EA3B3F0E06035C046CB5BD997D84B409F8F3B0FDA6035C081690E538CD84B4084206DACD26035C0028EBF8786D84B40861F6986D36035C0E1D8659C7ED84B40D836B01FD46035C07C8C2EEE7AD84B40B284F03BD46035C00CC81B6D75D84B405B3B8E85D66035C0F7FBB5E166D84B40E37FF761DA6035C027E07F3158D84B4023EF03FDD96035C06DE1C0B649D84B40A5242059D56035C0CABDEFCB41D84B40B736C65BD06035C0F6ECD0F340D84B401C28431FD06035C07292190E3BD84B409DA3E4AECC6035C0E87DB1FB38D84B40B01CE865CC6035C04608D9C52AD84B40A219853DC66035C0327E8EC825D84B4001A42A7EC26035C0EA8C1B0A25D84B4028B5E451C26035C0E282044415D84B40A8C5A210BC6035C05E9B1AAC14D84B40FFF08DBABB6035C0C1184F1D0CD84B40F49745B5B96035C09D4BCCC102D84B40BD575A04B56035C09145CECC01D84B408937DB05B56035C0AE5F03C3F0D74B406E18163AAF6035C04AA211B8E7D74B40E8D82CCDA86035C03A219ACDE6D74B403D1C3041A96035C0E95919D7E2D74B4001D91AA3AA6035C0C69550BED5D74B4057B59F75AD6035C0274D7190C8D74B4054C3E6D9AC6035C05601B98EBBD74B4037F8F2D2A86035C069968AF9AED74B4035E1B374A16035C0B1CA6D50A9D74B4099E5D24F9D6035C0217B1C43A6D74B406CB82E979A6035C01983CB23A2D74B40508769969B6035C0BF159B9A92D74B40D714D58B9A6035C033282CAF8BD74B403B196AEB976035C01F8CC59789D74B405040D4B7976035C0F3B80BC085D74B40E4D47980966035C056E3C8B782D74B40A3050AFF976035C0E62C70F973D74B40828510CA9A6035C04E5BB82D65D74B402D07923E996035C0D451B5C05BD74B40C828F470956035C0C64123EE55D74B4005BF35AF946035C046CDABDD52D74B40C683BF45936035C0101896E84FD74B401EFB8F3E936035C0307A67B24FD74B401EB18B37936035C05F1C71664DD74B4005A49AD6936035C03767948943D74B4007D26D04946035C0B75587F940D74B4066378C61956035C006C9014C3BD74B40E6808D7A976035C069CEE54E3AD74B4016D10D83986035C03DFA8BA437D74B40101642EA9A6035C0B52077A237D74B40E41F28E29A6035C0BBFF314E32D74B40EAB5D969A06035C02DB6279425D74B4042E1553FA86035C0240FAE9C1FD74B4082824D21B16035C096970AA91DD74B40316023A5B36035C08B5233A613D74B40858C2AE3BE6035C0E2E1282014D74B40A8DE19C2CC6035C00479BEE213D74B40B5342F2AE96035C061096B9D12D74B40DAEB30EA016135C09DF1DE8C0FD74B408A0BFCB8206135C018BC84A409D74B407DE8AC0A3E6135C0DACF711301D74B4054848FF5586135C00D0AEE1DF6D64B40021414A3706135C01AA75A79F0D64B40189E6AD97A6135C081DA0360E4D64B40FCBD9B638D6135C0927BD0EDDBD64B4030C60B49986135C0BC05129FCFD64B4085D379BAA56135C052FB9960C2D64B40BFCFA090AF6135C072062987BED64B402FEF203BB16135C04DB55A18BED64B40653CAC57B56135C0FE71A044B9D64B40F8244F7BCE6135C043E6E3BCB5D64B406C43F5B4DA6135C047399DB5B4D64B40F35F2F47DF6135C066AEFBCFADD64B4001964037F86135C05D2E26E7A4D64B408D0C15CE0E6235C0B2D026349AD64B403B8E0C7B226235C0535D7DFB8DD64B400F122EC0326235C00ABE688B80D64B40775C4E353F6235C021387F507FD64B402C5B97EC3F6235C02BC6A0F779D64B40D6A57279476235C0F18E7EAE6BD64B407FE3BD46556235C01E6F465B5CD64B4018E2FE5B5E6235C0DB9AAC1A5BD64B40264A47E35E6235C01EC969A158D64B4070B95303626235C0F123033255D64B40452092406B6235C04C121FD54BD64B40C2A028F87D6235C0441029694BD64B40AF217B997E6235C06173351A48D64B406BF8F30C8B6235C02A81A6DA47D64B40956D82418C6235C0A9AABA5E47D64B40EB4686CE8D6235C08126FF8B45D64B408BD00CAB946235C0CAEB86EB43D64B40927518DC986235C02FA7752A40D64B40F3971EE3A46235C0115C24D23DD64B4046F9C22DAB6235C0C9AB6FD134D64B407DACF2D5BF6235C078AA01442AD64B40423820BCD16235C0B7E2C1661ED64B40546CFC78E06235C0F9632A7E11D64B40523477B7EB6235C0BD7FBCD403D64B40519EAA36F36235C08E16EF1602D64B40396081E8F36235C0D3D820BCF4D54B40F5B43566F76235C0F2143740E7D54B405937644EF76235C0BBAE04E9D9D54B40C55788A1F36235C0DA5D9EFBCCD54B405C2FAA72EC6235C0A20B6A3ACCD54B405A8760CCEB6235C0E071A7E1C6D54B4072EC703AEB6235C0F13EA576C0D54B40DD6715C7E86235C0106B8FD5B3D54B40FB6938B4EC6235C08C3FB94DACD54B404C82ECBEED6235C05434916BA6D54B403BFED268F06235C08B6B344199D54B4089DC68D2F26235C0043FC2098CD54B40040847C7F16235C01E62ACA38AD54B40AB33E07BF16235C054EE6ADC7AD54B4026389296EB6235C040698BDE6ED54B408DFA5BF5E26235C0AFE11A5563D54B406285CAD9E06235C0534C7EEF57D54B40D7F5D810DC6235C08D47D45E56D54B405B3E4E37DB6235C0C1DD23894BD54B40C3D35264DB6235C0DAD72B103ED54B4069F7BAF7D76235C01D23940137D54B40AFD38930D46235C0630AA16232D54B40B4FD05EDD26235C08F29234628D54B401595F23DCE6235C06B1720601DD54B40AEC7E9DDC76235C08965B5A81AD54B40D1033693C56235C0ECA6783716D54B40C11957ADC56235C0208279C807D54B40340238D2C16235C071B7F7D5F9D44B40C65D90DDB96235C0F5B262B4ECD44B4025048AFFAD6235C064DB38B3E0D44B4077EDFC7F9E6235C0ED296AA1DCD44B40555DE24A976235C0A65720ECD6D44B407777A861976235C045D7ADF0D0D44B4086735790966235C0AAE1B549D0D44B4084756F78966235C005C06A96CDD44B402E72F610966235C0AEB92BA6C8D44B40C7132815956235C07742FDFCC5D44B40312B946B946235C027156443C2D44B40F1B31213966235C01A762108C2D44B403CC10B22966235C0CF38050CB4D44B40960D68E79F6235C07CC7959DA4D44B40C39A887EA56235C0E91402759AD44B4024E4B0F8A56235C0D2CBA9EF95D44B406CB5A595A66235C0779C4EEA88D44B40F83FCFABA66235C00949898880D44B403A35618EA46235C0E818F9A67FD44B40C1867EEEA56235C0AD72EBB871D44B401F37AB09B56235C004FA761C6FD44B40C715D656B76235C0DA2315236ED44B40DDB4A446B86235C0B5A0AC9F60D44B402A32E9E2C26235C0B057B3C852D44B4097B8A44AC96235C05951C4D547D44B40612AB7A3D46235C09DA4475039D44B40ABAB3B57DE6235C0E8B6347035D44B402661FEA8DF6235C093C7685C35D44B40E0291AB9DF6235C074951FFB2ED44B40C45CCE57E56235C010074D6D28D44B40FFA7FF8FEA6235C0E23A8C561CD44B4075C16333F36235C09C1D56BE15D44B4031C88411F96235C0B05AE52614D44B40357A6E73FA6235C0ECEC04750ED44B40BE423547FF6235C087D81C7306D44B4037BB9B2C086335C0D9AFEB2000D44B405A4A459A0E6335C04C576831F1D34B401A94C8711C6335C0EF4D0BB2EED34B400C78A4811F6335C0E8697CD5EDD34B40DE75CCB7206335C0607AA1C6EDD34B40F8B1BB92206335C00DC4163CE4D34B40488686FC2D6335C0E3BA1D10E0D34B402C67BBCD326335C0D3D11EDCDCD34B4042FD4CEF356335C05EA3CF3ADBD34B4016F9038A386335C0C23B2CF8CCD34B403040A45E4B6335C04974C11BBDD34B40981394FC586335C0644BDD33B7D34B4048B8C3DE5C6335C0FD7D6608B6D34B40578D959F5D6335C0C46291C0ADD34B4002A29AD7626335C09186B6DCA4D34B40F4563F31696335C0818C5F1BA0D34B40FD060B526C6335C00539E7AE93D34B408E64EFCD736335C021B4A61E92D34B40FD2B8AF8746335C09A14A16491D34B407F0376567D6335C0F27CB90291D34B40C7832A1A806335C0D8D90DED8BD34B40DFB150729B6335C082F2658984D34B40C53437E1B46335C093DF090A7BD34B4006C8C9B9CB6335C0AB296D8373D34B40BFEF94C0D86335C0CA53946C72D34B401BC7AD27DC6335C043CDC9BA68D34B40ED2C18C0F16335C0F0F8E1545DD34B4092B88232046435C0DEE0440D56D34B4025EBC09F0C6435C0DCDF2CA24AD34B40993CD7AA1D6435C0553989643DD34B4041F18A9E2B6435C0FFE2D9212FD34B40EA46526E356435C07FE864252BD34B40FAD821E1366435C01DF3E5E621D34B40EDD58A983E6435C0C37A790D14D34B406A037AB5456435C0CA52E5C905D34B4017B090D2486435C0A3251595FDD24B40BB077346486435C031106FA7F6D24B4037DF6768526435C0993D6D1EE9D24B4023379842606435C09A534D90DAD24B4025CB8BD1696435C008D1AA5FCBD24B40EC3A81D46E6435C024E597AFC6D24B40F6DEFBA56F6435C069BF3FBFB7D24B407C89A60A706435C0378EF1F5A8D24B40D435CD086C6435C0D349ABB19AD24B404906E9B9636435C0C31F1D4D8DD24B40C30CCA52576435C013E6681D81D24B401D844722476435C03DA00BFA7BD24B40EE3BE4B63D6435C0A60643ED73D24B401A63FB3E386435C05087630C73D24B40CCF67282376435C0502F92D065D24B407001C6F3296435C0211079E559D24B40314FAEA2186435C0E48050984FD24B4004275FFF036435C02432D0D54ED24B40BA6D8F35026435C01F08275C4ED24B402C69AFE8006435C00127A56E4DD24B40681E31D9FF6335C066C9B88B49D24B40792100A4F96335C0B2485C8D48D24B4020FC6698F86335C01C18930048D24B40B1F359E9F76335C0FAFDA46640D24B40477FA397EC6335C05F5C79503FD24B40167B98F1EB6335C080FC6EFF3DD24B407FFB1AF5EA6335C0B74A1CB72FD24B40E1C6B98ADD6335C072270A452FD24B405ADEC6EDDC6335C08CA303FB2CD24B406CC13BDCDA6335C014C9C0852CD24B4003CF5D5BDA6335C08F2037D42BD24B4004DCFACAD96335C0CE99666E1FD24B40587AFB15CB6335C0334EC86414D24B404F44B1F8B86335C031F60CFB0AD24B40DD6D34E2A36335C09D66EE6A03D24B40D14DD9538C6335C0C9B02E8401D24B40C3DCD935856335C06A10DBE8FBD14B40E1FDEE4E6B6335C0B9E72B6DFBD14B40E431ED66676335C098BDF654F8D14B408E0741B35B6335C05D716ACCF3D14B40D4EBD0D03F6335C0DD84E1C2F1D14B40A2C7EEEA226335C083266746F2D14B40ADB4E4C8056335C0365C86A1F3D14B403ABBCB14F96235C0DAC8B8A3F1D14B40E9C32410F76235C07492EC38E5D14B40580BF005E86235C005C0EAEFD8D14B40B1A75A74D66235C0162347CCCFD14B40E12D008FCD6235C05F4F1568C0D14B4086CBA05AC26235C0E3BF1A6EB8D14B40ED508A0CBF6235C021CB026CABD14B40EC622EB2B96235C08E604E88A7D14B40A3C7F9EEB76235C02102057E9DD14B407E481CF6B26235C04651588898D14B40D1011DAAB06235C0034D50618FD14B408E1754F3AD6235C07287ECBB6CD14B405B03B4D0A66235C0E01A616852D14B406A32262DA36235C06A71F2994CD14B4089CF536DA26235C000E9940B4BD14B405799FBC5A26235C05B204E843ED14B400B69EDFEA36235C011B3241830D14B407CC6EC9CA36235C0F664C51A2CD14B4023578059A36235C00482571E0FD14B40A401534AA06235C09D7D3DC70DD14B40A9C98A21A06235C0CCFEF95304D14B40DEA7BCE19E6235C0FF6F0CEE03D14B40EE37E4D19E6235C01EAFD448FCD04B4019886D029F6235C0E00A3A52F5D04B40B5E5FE889E6235C07B1D851BEED04B408C5213469E6235C05A4202C6EAD04B40EB72FD0A9E6235C0D59B1220E5D04B40E7C421779D6235C0D2C77290DCD04B40816986DB9B6235C062CBD912D8D04B40A0F55EA09A6235C0B706293BD2D04B4079481F63996235C005AFA7AFCCD04B40D8D6DD509A6235C009A3DBF8BCD04B4061FC47379B6235C09BF43FA8BAD04B40CFE2869E9B6235C060FCAD35BAD04B408383F9B19B6235C0A66BE5EDB1D04B40DE2F4F109D6235C0021EF636B1D04B40B4A23C2D9D6235C063B687C6AAD04B4028E524269E6235C093A5B5D1A8D04B406EE501689E6235C0D264BE26A3D04B4081D9970A9F6235C0CDE6C3C59CD04B4081F01073A06235C05D247FD990D04B40A164C5A9A16235C0D72DF66783D04B402C578571A16235C030B2785681D04B4025B80A5EA16235C016D76A8C79D04B403329E7EBA06235C09BD56EB569D04B4061205DC1A16235C022C0742C64D04B40FACE70BEA16235C0FE96D94557D04B40AF4AF302A16235C05D0D1ADE4CD04B40D63A488CA56235C0506492C53ED04B401016BD2EA76235C03D84F4953BD04B406C6C63BCA76235C08B833B9137D04B40A7132210A96235C0ADC1BF6235D04B40737B8793A96235C045FF280A33D04B4013B86384AC6235C0DDD2B59726D04B40FF4F489DB76235C07F2A026B19D04B401BBE9536BF6235C0A9ECB21E14D04B40AC2D937EC16235C0D7B5B98500D04B4040013DFBC56235C0E3C4F8ABF7CF4B4048D8B844C66235C02BF2B9BAF0CF4B40029E6804C66235C02A3CB63CEBCF4B4012C9E970C56235C092667B88E6CF4B40003DC40FCD6235C0BEF11E28DDCF4B40044FAD1DDB6235C01289A5CED0CF4B40EBEA19CFEA6235C06C69CB4BC3CF4B40D28184ADF66235C051197382BACF4B40C08524DEFC6235C01B64E5BDB5CF4B40823C1C15016335C09BFDBB90AECF4B40A2FF6AC3066335C0960F370FA9CF4B4050BD6BA10A6335C04704D37EA6CF4B4027CF77810D6335C0D788366B96CF4B40CD932DE11B6335C023BDA33293CF4B4025EF2B171E6335C08B4CB50F8FCF4B4095BA351B226335C05B5BD47B8CCF4B40B0477943256335C079F3FF5185CF4B40251B1D382D6335C0AAA8CEE077CF4B406E919CB03A6335C0D590BAFA72CF4B409AD5F3AE3F6335C0D648EDB067CF4B40642B7F73496335C0A69645D15BCF4B40E681205E506335C054C40C904FCF4B408CD46250546335C054B9F34F4FCF4B406083FE5C546335C078373A964BCF4B402113F94A566335C0E9E0224E45CF4B40CF2E76FD586335C0ACBB643043CF4B40AF521D645A6335C0470C765038CF4B404FC095D6616335C0F870E20B2CCF4B4096A9C185686335C077E871A21BCF4B40BD0299416F6335C006DB10A11BCF4B40A8BC53426F6335C0B4903A7212CF4B40E25BD588746335C08A3AABA80BCF4B40BB1E5EEC776335C0A37728900ACF4B40D39A11BF786335C09BDCFB7A06CF4B40BDE6589C7B6335C0A90518E405CF4B409558DCFE7B6335C0E7C032E9FBCE4B4044B4CDB7836335C0890DE78DF2CE4B4002A9D8E7896335C0D3EEF01CEFCE4B40E9BEF4CE8B6335C07D50CE01E5CE4B40A2B51949906335C08E4EB2F7DFCE4B4066F678FA916335C056A59BAFD9CE4B40918E26AF936335C0C0012CD5D5CE4B40FF31237C946335C03312BFC6CCCE4B4014E8698C956335C0E268855CCACE4B40FC9D7C9D956335C0ED79CB20C8CE4B40E731A9A0956335C07DFA1FA2C6CE4B4097BA579A956335C0BF88911DC1CE4B40C96AE935956335C065B5A20FBECE4B4070E689D3946335C09D0DFBCBBCCE4B406E1116B7946335C09F4ECC75B6CE4B403A62C2E4946335C0409EDC1DB1CE4B405654E2B2946335C0282579A3AECE4B407367348C946335C0564735D4A6CE4B4092BC42E1936335C04CF99396A5CE4B40B2AD31C2936335C045A9988B9ECE4B40A34FA6FB926335C03F7C5A989BCE4B40ABD409C8926335C088FB25A097CE4B4095577CD2936335C0F41C839D91CE4B4097A8AC84956335C0D0C02B3D8BCE4B40232BECE7966335C060E8457C80CE4B40AD1AB48E986335C0CDF4323380CE4B402DC0B799986335C056BD2EC276CE4B40529224FF996335C068DB4DDA71CE4B404FB276E29B6335C04EBEBFC66DCE4B40BE7593D69C6335C05B6DCB7F6ACE4B40AB2342C09D6335C091568DE167CE4B409AF31A699E6335C0BE1A272F67CE4B408EF250919E6335C036163C0266CE4B4028CF5B59A06335C051003DB763CE4B40D402A95DA36335C0D8D6A2775CCE4B40F5A8D802AC6335C03AAFEFC25ACE4B4090E602D9AD6335C08F66E5B355CE4B404C7E44EBB26335C0DFFEB88053CE4B40C09B69F7B46335C09B07FCF947CE4B4078FF34F9BD6335C01C56944E43CE4B40D94B75F6C06335C0294E87C732CE4B4076487B85C86335C0566CC9012ECE4B40B9197CDEC96335C02205AA6D26CE4B404B5CB06DCB6335C09E6F52E21CCE4B40E31DDDAACC6335C0CE28E00C09CE4B4003698557CB6335C070F7DF6C03CE4B40776CFAD9C96335C01AD1619AF9CD4B404B88E33FC66335C0561A1D63F4CD4B40C03B87CBC36335C0F9A8C846F1CD4B40CF46B939C26335C06628791EECCD4B40C7D44072BF6335C0B286EB4EE1CD4B4012C5BA44B86335C0D5D9140AD7CD4B403930A9B2AE6335C05CE7CE93D2CD4B40639E46E3A96335C01DAAF51ACCCD4B404EAE5B3EA26335C001E4E8B5C7CD4B40A0C080939C6335C045A0A148C2CD4B40ACB0D997946335C04B6BD53FBACD4B40DB693B418D6335C008BB5236AECD4B409ADA1A8B7D6335C0B8C78939AACD4B403AD80765766335C017B06E17A6CD4B40E58CE994716335C026D1F6CCA0CD4B40671B00AC686335C0E2907F069DCD4B40213B06F4676335C00910DAB49BCD4B406E7CC77F676335C03887997499CD4B408B2DDBAB666335C0058259A298CD4B4067F79259666335C046D3524D94CD4B402C07B1DF656335C09448DEC985CD4B40EB2D78EC5F6335C0C02A13F377CD4B403AF7B3CC556335C010D0B92772CD4B403F4CB3734F6335C050109D2772CD4B4016DD17744F6335C0E23B13546CCD4B408B1E069C496335C0797B418E66CD4B4094C09EE0436335C01D298AE163CD4B4061A7BC1A416335C0A4F80F865DCD4B401F2B1C103C6335C04257862C52CD4B40FD5F58502F6335C078A6EBE147CD4B40064A86BA1F6335C003E672FC46CD4B402B1C3FEE1D6335C0472ADF8F41CD4B4015DEA379156335C0337A2F203ECD4B402AF49C6E146335C0A67614C72ECD4B402134FB440D6335C04248674720CD4B40EBBC466D016335C01E00C4C214CD4B40E4C99758F36235C05D31E1DB08CD4B404A93B98AEB6235C041EBE1DA04CD4B404BCBE022E86235C0D66C7191FDCC4B40E7A7B175E06235C06BE6C589F8CC4B402F1086EEDB6235C03A9EAC55F3CC4B4084092240D66235C0D35AED4DE3CC4B40EA82E73AC06235C0AAE5BF88DFCC4B405EA669CFB96235C084DC7452DDCC4B40F030595CB66235C0C504F27DD8CC4B4013399637AD6235C04D33CD3FD0CC4B40177ED9459F6235C0843CD37CCECC4B40290E268E9B6235C0757E1A3BCDCC4B40EFCD177A986235C0994A7C7ACACC4B40F9395AC5936235C09C23122CC8CC4B40AD4477F48E6235C0BE206F37BDCC4B40D4C95B02736235C0E1CE229CBBCC4B40E50243F06D6235C0C7484DF5BACC4B40764242736B6235C0B018A80AB9CC4B40A64EFEFD656235C066094E6AB8CC4B40CB640755646235C0E40196E1B4CC4B406D0DDF515C6235C09D8C99CAAECC4B400F38A3A74D6235C034AEF4EDA8CC4B40B8074C863B6235C047EEE2C8A6CC4B406FA7FBE0356235C0A5B722F6A3CC4B40C8B7844A2C6235C0F7E8A8D59DCC4B40B647E8C41E6235C096FE42659CCC4B403758F8031B6235C067B8146A9ACC4B40F1065854156235C0D926D8639ACC4B40155A8E46156235C04F65A03E93CC4B40B8F61C4A036235C0CAEFAE5D8DCC4B402A08A6C2EF6135C0E8CA914C8DCC4B400C565480EF6135C0735DA5EF87CC4B40D4E40CE8DE6135C053244DDC84CC4B40AB189BAAD16135C09A84F3A881CC4B40E724C332C66135C0069993357FCC4B40B3ACBD20B96135C06CC2B8DD7DCC4B40C85B0765B66135C04A366A827CCC4B40FE129243B36135C01B88A9707ACC4B406993FD2BAD6135C0DA935A8D74CC4B4055D50DDFA76135C0CB68FE0A70CC4B403DA6DB1EA26135C079C58C0870CC4B40608DFD25A26135C0DA14A8E26FCC4B405EFE6DF8A16135C0E73535026ECC4B40A1B694C2A26135C0A31CFA445FCC4B40B3539A8EA46135C01298658F50CC4B4003E62E0BA26135C076E0A6374BCC4B40CACD5E859F6135C09666DEAF49CC4B40728F309E9F6135C019EE8B8844CC4B401FE77E489E6135C0B637B75640CC4B4005D496EA9E6135C0A824D9B030CC4B40C776E3679C6135C0A288977A21CC4B40BFFE2E17956135C0067ED11E13CC4B40FC50DE2B896135C00B4F2DEF0CCC4B40C003FFD6826135C0A677423CFBCB4B40C80815716B6135C0B83E315FFACB4B40F0F82EFF696135C0ABF5193EF5CB4B409F84CD60626135C010626257F3CB4B40261F767A5F6135C01A4EC741E0CB4B406E56FD97416135C0292C0396D8CB4B401E71ACBD336135C0691420ACD6CB4B40EDE8C023346135C0CFB1560BC8CB4B409EA82AF0326135C0CCB080B0B9CB4B4084CC0A882D6135C073BC8A11B8CB4B404E7DA1692C6135C00AA4B5CEB6CB4B406937CFDE2B6135C0584CF88AB6CB4B405F4D5AB62B6135C06A0FB4F1B3CB4B40B9C906052B6135C082246F47ACCB4B4006357DF5306135C01A228D659FCB4B404E4FAC2A376135C0F72175549ECB4B4015239888376135C06A3E4B498ECB4B4081CB05713A6135C0AD302C367ECB4B40070A5337386135C0B3700D926ECB4B400509F8EB306135C06AB0D1666DCB4B40EE00872C306135C0B31DDB4C60CB4B40391EFEAB256135C0E7DFEA2F54CB4B40ECD6159C176135C014EC115649CB4B406BD3254E066135C018F1EA4A44CB4B40FF20F36CFB6035C0204435EA43CB4B40F2CD2FC5FA6035C0BAA7B83043CB4B402004360CF96035C0390D14FE3FCB4B4029DF4626F26035C004393F753FCB4B40530A9B91F06035C0B3B845C933CB4B403DE20C26DD6035C0A8F2D3CC32CB4B406C5C3932DB6035C04187CC1629CB4B40B574E980C46035C0E576487C21CB4B40416D6325AB6035C0D9887B311CCB4B401FA9C2CD8F6035C0BC7B49D41BCB4B40ACE8A34C8D6035C03C9B363919CB4B40F26F498F746035C00033B58318CB4B406D69CA605B6035C078659BE218CB4B40E1D3E8A1536035C0136090E318CB4B4087CDA3204F6035C0AF56240D13CB4B4089EFEB0A446035C0CEC4B42B0FCB4B40F75C8DFB396035C0F57831880DCB4B407C8AA93E376035C09196804506CB4B405F2CB1A7266035C00DD61CF702CB4B40FD9F380C226035C0DB1F1A9BF8CA4B40482E6C3A0E6035C0D17C3205F0CA4B40E1CEE9A0F75F35C02576233DEFCA4B40FF2C2D30F55F35C0B53229F6EECA4B40AD66A626F45F35C0D7E5846DEECA4B40AC9A1B7AF45F35C0990E69D7ECCA4B406C084F50F55F35C0BB629971EBCA4B401DEC2A10F65F35C07A6F69E0EACA4B4020E04472F65F35C0CB66D1EED5CA4B4071C482BBFF5F35C00D6D9ED8D2CA4B40A0A0F86B006035C019652372CBCA4B4048196F7E026035C0DA49F11EC8CA4B40B2434F0A036035C0A2B7FC31C1CA4B4072CF367F056035C08AA05054B4CA4B40DCE3585F086035C022F6D25FA7CA4B40513D41EF076035C0EAC88F4DA6CA4B401883BCC2076035C030577542A0CA4B405598D8EF076035C050EF2F949DCA4B4078447D56076035C085201F5C95CA4B40CC45CF760C6035C075EC875B95CA4B40A21914770C6035C0796832A88FCA4B403A964DB8116035C0057CB4098DCA4B404E5C152B136035C05760E4EB83CA4B4052BA03CE236035C0E94268547FCA4B40B5FD60D3296035C049F6DEB579CA4B405BD29441336035C0AA2305346DCA4B40780D5262426035C07872FD955FCA4B402E0543A84D6035C00BF75EF053CA4B4021549F6F536035C05B44CCDB53CA4B40ECF4BB93536035C04B572B9D52CA4B40F5BB647A556035C01B678B4E50CA4B40FA0DAA77596035C0CDFAB9DE44CA4B404E6B5A19686035C021FA21B943CA4B40D59FB458696035C02544649C32CA4B4074E73CEC776035C04D03001231CA4B4063E632E7786035C0E4C401C024CA4B40DEBEF50B7F6035C030C2DCBF1BCA4B401442D82C816035C08CBCA77E14CA4B408CFE626E846035C0ECED550D13CA4B40EF9A0FC3846035C0064ECAB70CCA4B4083532641856035C00CC96FCB01CA4B40E98963ED866035C0322FB522FDC94B400D7B6336866035C091CD070FFDC94B404079CB45866035C093681C00FDC94B404DC67C55866035C08A14232EFAC94B403D39BF768B6035C0692B8D21F9C94B40433A6CD88C6035C03EC6B894F2C94B406AD647949B6035C0F891C38CF0C94B404A8F554F9F6035C0534CDA9FEFC94B40FD349EFCA06035C010ACA33FECC94B40B89D6E05A76035C0CB1D9AF3DEC94B40D59C93D8BA6035C0F85DF376D7C94B4052C03F60C26035C0E80B07DFCEC94B40EAA4A382D26035C0679F3E2DC3C94B406AB36898E26035C06C13194FB6C94B4013043626EF6035C0CA32BD91A8C94B40606BC6E0F76035C0994D8C479AC94B40C705C493FC6035C0463834C68BC94B40620C0223FD6035C0947FAD647DC94B402EC7258BF96035C0D1E302A578C94B40DF4B05EEF66035C00984DCCD72C94B406B5FC8A1F86035C0E444537672C94B40084163E9F86035C07F99BD7164C94B40B051BADBFF6035C0C8D45A0656C94B40212A14B8026135C03CD0D9AA4DC94B4061F937F9016135C0FB1BEAE34BC94B40BC7F3026036135C0863DC3033EC94B408415F627086135C09B0380683AC94B4001FCF6F0086135C020AE78E92BC94B40A24CFF000A6135C057E3BC7F1DC94B40D73E28E9066135C08BA3148513C94B40655AEACA016135C03F62655713C94B4064105045026135C059D6787708C94B402DB84718176135C04C82F3DF07C94B4020222DE8176135C082E96BF000C94B40CAD86DB7256135C09226E33BFCC84B405E8158702D6135C09BCCF3F5F0C84B40C25097533D6135C0E241C48DE4C84B40719A90E7496135C037D3A949D7C84B404BB9F6E4526135C0F770D5BDD3C84B401405713A546135C0F2BEE5DFD1C84B409E50D86A576135C022159457C5C84B40F7343ED5666135C0E41F3FABB7C84B40DF2DCD5A726135C071DA7232A9C84B401DBABFB1796135C0259AD8499AC84B40C54F17AB7C6135C00FABE5508BC84B40F04AC9337B6135C03C0178A77CC84B40B9DA3855756135C0F74B70AB6EC84B4096DFFA346B6135C021C958B661C84B40F93CE5135D6135C0C049E1705BC84B40C2B85FEE546135C0257915E04FC84B40CF299139436135C08EE41AF045C84B40A305E2552E6135C099B417E03DC84B40A1C612C8166135C0D79947E337C84B407080D225FD6035C08749B61F34C84B40CD900712E26035C03ED04DAD32C84B40905AC438C66035C0F9B73E9533C84B4006C0014BAA6035C096C3C5D136C84B40D5823AFA8E6035C0E0369FE638C84B40AE1EE83F826035C0267136B63EC84B4027E1C2FE666035C0DA22B42741C84B405743D0D15D6035C0C21FB94944C84B4006E614FF536035C09BDC089944C84B40C1A22E2D526035C0FA1FCFCE48C84B40BD43266A426035C03E6A541F3CC84B403BA0E684326035C0A44479183AC84B407243B8812F6035C07DAF337339C84B406AC3DD892E6035C0E6FE2D4438C84B407AE85ABF2C6035C059DB507E35C84B4018C164F9296035C0D064AC422CC84B40DF331AF01E6035C0B94325BB25C84B40DE0F672F1E6035C0D08DCB7416C84B4056983A93176035C0A6F1466515C84B401E6E9DC0166035C0EACC5AEF09C84B406DBE9BC4266035C0AC3A21E2FCC74B400C8419A8336035C0338413ECEEC74B40EE8172983C6035C0A122C463E0C74B4023E5385E416035C0A1C9D231DEC74B40FB2038C3416035C0A7A93354CFC74B407645803D426035C0D4EC2D9BC0C74B402C01A25B3E6035C0DF167763B2C74B402BDC0F36366035C02FF5E7EFA5C74B40C8854BD52A6035C0C206692DA3C74B405288A3AB2A6035C0D87643B695C74B402F4C003C2E6035C065325C1287C74B4076E4F7D72D6035C07E4462A378C74B4029D5FA3B296035C04D8F7AC16AC74B40A9582F84206035C02B4A6CC15DC74B400AE9D2E5136035C04DB336085AC74B40A1BB95C80E6035C0D49258B459C74B4070E789DD0E6035C0EA7353E447C74B407AA2ADFF0C6035C023868D4447C74B40E91E68D20C6035C0597D388F45C74B4001B9F4630C6035C0F4F08A9437C74B40FA63D6D2066035C0689D87F82DC74B40850D570A006035C0E0CB5C961FC74B4015EA984CFB5F35C059EAF4DA1DC74B4025ACD72AFA5F35C0F02F2CAC1BC74B4074F33F8BFE5F35C07AA0D2BE0FC74B400CD8E8E10F6035C075050C8002C74B40E27243741D6035C0906DD745F4C64B4074EA31EA266035C05F0EB60BF2C64B408FEEE407286035C0BCA79C3EE2C64B40F7AD61502D6035C0C63EE989E1C64B40BC83C9522D6035C08D5822E7E0C64B4056B383C02E6035C04E550821D7C64B40D2D66D993E6035C04D5134C9D5C64B4078320400426035C0CA6D9B11CBC64B4022CE82AD556035C047F308D4BEC64B409B23DBF0656035C0D66F025FB1C64B409D53B961726035C0CF4DF3FFA9C64B40720267E2776035C01AEE0C409CC64B40EE9BDFF07F6035C04F5CF6018EC64B4001616100846035C01B20E4997FC64B4083A4EAF8836035C0C8FC025D71C64B40C6A9A7DA7F6035C0F1A535036CC64B40DDD0EFB17C6035C0B23515386AC64B4023C4D3A77C6035C017CD4BC45BC64B40006F1A1B786035C036758CDC4DC64B40439D15706F6035C0E922E9D540C64B40041DCDDB626035C0708848F73EC64B404CDBF0B1606035C05A5A228F33C64B4082163B30516035C0B5C26F8129C64B40D26B5FAF3E6035C0260E130721C64B4015B41098296035C0D127C24520C64B404692D0FB266035C066D3778D1EC64B40B0E2D163236035C0B9682AA91AC64B4053BADA881D6035C016F3F7B010C64B40D39B88FB086035C0D62FD28D08C64B40E0D732C5F15F35C0B47EA87202C64B401EF12777D85F35C052F3B285FEC54B400991CFAFBD5F35C0462E83DFFCC54B402DE1CA16A25F35C06D88AB7BFDC54B40F158A8BD885F35C06AA04956FDC54B407A4EDDCD825F35C0A4999BCBEEC54B408AAEA6246C5F35C0339FDE82ECC54B40C3CE9677685F35C0CDFC8EB7C8C54B40F2D4C4FB2C5F35C03C0FD398A8C54B403995C83EFF5E35C07461855790C54B406A6963E3E15E35C0954F6F0A88C54B40E23B7D17E25E35C0806E3BB979C54B40AE3AD752DE5E35C059C43E9C78C54B40EA97BEDDDD5E35C01126462574C54B40261D1842DB5E35C0B408102067C54B408333A757D55E35C0A15AB47F59C54B4019A8B7BCCA5E35C000F281EA4CC54B4015988A4CBC5E35C0B8C62C4D4BC54B4082E02EB9B95E35C0860395B848C54B4016DB23E5B65E35C0B575CF7E3DC54B403D1D2CD1A55E35C0673674C933C54B40777331BD915E35C0363667902DC54B40BF960713805E35C0A8095E322DC54B40D51AFDEA7F5E35C082122E2529C54B4084770E8B7D5E35C0183655EF25C54B40988740CC7B5E35C0F4B9A5A124C54B40D281C1E57A5E35C019AE09C91EC54B4020B88878775E35C0D04FACD61BC54B405F7A383F755E35C02CA1D44A08C54B40F609D434615E35C014F0088A05C54B403D0513895D5E35C0C83D4D3F03C54B40EA29A1615A5E35C0E14E1FFC00C54B4060393D2B575E35C0D3F3C04FFEC44B4092A4C43A535E35C0CE79CE86FBC44B4071DAD5F84E5E35C05A408DF1F1C44B404C84451E3E5E35C0E7DD3912F0C44B40CF89B8C3395E35C000C25A27ECC44B40ED2CD9C5385E35C02CCD66A9EBC44B40C89CAC94385E35C099702F70DCC44B4055683517305E35C05D6A662FCEC44B403D812EF5225E35C080ED7D4DC1C44B40E46EFD8C115E35C067CB0D27B6C44B40CA1AC05BFC5D35C0FDBA735DB5C44B4089DD2196FA5D35C0B719C03BB5C44B4073A43D3CFA5D35C079BCBAF5B0C44B4088ADC2ECF65D35C0A5AF7839A6C44B4064B8AEDEEC5D35C08488D96F98C44B403F48A98CDC5D35C00A34274C8EC44B402991E432E25D35C0A530C95E6DC44B40AA47D630F05D35C0C5784D3A65C44B40B4A9F5F4F25D35C0D3D4468638C44B40567C9E64FE5D35C08A47443D2EC44B40CD264EF6FF5D35C06AC8776A19C44B403B3AF501015E35C0161D02AB12C44B40478272E5005E35C0FA7B0F16F6C34B40BD9A3B84FE5D35C0FBDA50B9EEC34B409FE0155DFD5D35C0E23F27EAD2C34B40EA28B9F4F65D35C0CD7DBB06C4C34B409555AD39F15D35C07B19BA41A1C34B40B3AB3C4ADE5D35C00994AD609BC34B40C740D3B1DA5D35C0D01F762181C34B40881121D9C85D35C00B3A914579C34B40BC0559BEC25D35C0D8E8C44D5CC34B402F9EA854A95D35C0C0D30A3A4DC34B4098BE59C5985D35C096D37FDE33C34B40BF8695AB765D35C089B78C471EC34B40DFD9DF085C5D35C00DFF52061BC34B40F00996D5575D35C0B50233D905C34B4072A7B2443B5D35C00ED3387E02C34B40FD49E686365D35C09BEDFE87EEC24B409778FFFE185D35C0BA191798E1C24B40406E41E90E5D35C04FEF68E8DCC24B40CEE50F66095D35C062BC0F10D5C24B40DB90E736065D35C09A097DD2C7C24B40340C92CBFC5C35C09118877ABBC24B400B2CFDC8EF5C35C0BB85A31DBAC24B40745EF515EE5C35C09A7CA544AEC24B40224E3F9DDC5C35C03066C2A4A5C24B403BDDC615CB5C35C0DA0FB65DA5C24B4054F55DCECA5C35C09A10F54AA2C24B40AB7A3F68C75C35C078AE493297C24B40D3DC430CB95C35C018BCD24C8DC24B40F4F13BE2A75C35C0AFD4CE328CC24B403A36EB56A55C35C0EB351D6088C24B40C4292DB5A35C35C0ED5E7C237AC24B40540EAFDB985C35C016D25D066DC24B40C6ED31D7895C35C0FF048B636BC24B40A8CF6C33875C35C0FC565DA369C24B407BDF4181865C35C0CFB987855BC24B408BDE93477C5C35C0EB418D6752C24B40EF1BC64C725C35C0935163BE49C24B40413A0E48775C35C0BF6DA9113BC24B40A80F8A317B5C35C09252883F2CC24B4053CE8CC57A5C35C0B195C72F2AC24B40D1B5DF687A5C35C0DFF8463725C24B4096F7ACFF785C35C0D01AF5B721C24B40E2472B74785C35C02984B92714C24B40E8458280725C35C0E6E8303407C24B400073DBE3685C35C0A6DD06D2FBC14B4010E9D78E5C5C35C0184E9180F9C14B40DF13FE795B5C35C05C8FC8CAEBC14B405148ABF4525C35C0A75EABEEDEC14B40D0C888A1465C35C0F36BAD38D3C14B40084ADCC9365C35C0D84EE012D0C14B40381D99FA305C35C093FD03E1C5C14B407EFA8083245C35C0774D3504C0C14B400CC611CA1A5C35C09B7B3488BFC14B40111ABDB81A5C35C09EAB0A94B1C14B40B4B576B8145C35C03F5B2044A4C14B402C17C4D90A5C35C0F0CB97679EC14B40F0BE1972045C35C0166258CB90C14B40B23DBA21FA5B35C07C12136183C14B404D8350B3EA5B35C0F936CBB381C14B40ED4F52F6E75B35C013CC36F377C14B4046CE4BD4DB5B35C018A695E476C14B40A547904EDA5B35C0261E2C7D6CC14B409B5414DDC85B35C04DB8C8CF65C14B4010536A01C45B35C08EB1946C62C14B4052FC62C6C05B35C0FC62ECBB5AC14B4060667315BB5B35C0FEE4302A4DC14B401C31C1B7AB5B35C0702AE67149C14B40891EB2B6A55B35C005F36FA844C14B409E753D87A25B35C0C1E040C335C14B4049722671925B35C007575C4134C14B4026E5D273905B35C047EB185033C14B4021A3629D905B35C04D14082B2CC14B406FA412BE905B35C0E01673F61DC14B40F62448FE8E5B35C06D72AE0E10C14B40011E2247895B35C0C72307C402C14B407C1DA2B97F5B35C09CCD261CFFC04B408EC589D57B5B35C05FB170B0FCC04B4033DF99D87A5B35C035019485EEC04B40E2445763705B35C004743746E7C04B4077A6D351685B35C01CB7C741D9C04B40A4DA82F06D5B35C0C86D773DD1C04B40E8809BF06F5B35C054B4F448C1C04B40210C8460715B35C0C5B2786DB1C04B400DCC28C66D5B35C098124E1EA2C04B40A1F9BB3B655B35C07C1BC3CA93C04B40143956FF575B35C0B75700DB86C04B40A40A3371465B35C0723A13AD7BC04B40EE04F510315B35C0F906A6FB79C04B40E1C5C63A2D5B35C024B53D0D71C04B40A28BD233155B35C0287C8F956CC04B40DEE79992035B35C03D6CA61067C04B401F08A4EEFC5A35C0F07B830E67C04B4067F701EBFC5A35C0E79A766364C04B4078C0AD18FA5A35C0C5EC638B58C04B40C56E675FE85A35C0CB6DC9A453C04B4041DB3B40DE5A35C0E882B57752C04B4041FD29A7DC5A35C07B98F6B347C04B406DD54459C85A35C0ADB0CB7145C04B4014D3356EC25A35C07D3523953DC04B40528D9181B75A35C0E18A290F3DC04B407DBBFAA7B65A35C018ED115432C04B40FFC6F927A25A35C08ACA839D2FC04B404B741BF19A5A35C0347035672AC04B40DEA5F756965A35C047CED06326C04B407031A2F1915A35C098D33A8F23C04B40BB1CF91F905A35C0AF9B402718C04B40D0820E66855A35C0C4BF51DA17C04B40DA9D7503855A35C0277301C80DC04B40E98A31037C5A35C0FBFF401A07C04B40E226777E735A35C032D3EFE403C04B40F2021B9C735A35C08BBBB340FDBF4B40E19F75E9715A35C0ADDFCF68F3BF4B402DF163CB775A35C09DB5B4D5E7BF4B406F743CD77B5A35C0B219C758E1BF4B40635AC2557D5A35C0E3AEC2F0D2BF4B40D0BBEC947E5A35C0C88D73E4C6BF4B402B253A2D7C5A35C04AAB6B57C0BF4B401306B5B9805A35C0F9F2D03EB0BF4B406A1ABF46865A35C063AC09BAA8BF4B400E56B467865A35C0A318EAFF9DBF4B4018D90526895A35C08468597190BF4B40D6A44BF5885A35C07382FF0983BF4B40E5975F25855A35C0CAD40F157FBF4B402C288978835A35C0883EEAEA7CBF4B400C131681825A35C0E2816DC877BF4B40F2A77817805A35C0D0872AA463BF4B400DBE15E9715A35C05FE31B2D61BF4B403905B58B6F5A35C0D17BED1456BF4B4012A7980A635A35C0A733428A52BF4B40DF52C0B15D5A35C06B06A4FE51BF4B40DC3A0CC55D5A35C0F5F06C0A4FBF4B403D8A7DB75D5A35C0C11FE97C3EBF4B406A0B29905A5A35C0A5FB99E73ABF4B40643C6C485B5A35C05D57567A2BBF4B4051B9B0A6595A35C04310C7651CBF4B406A974858535A35C02F35E1A81BBF4B40FD6AC6C9525A35C0529FE4EA17BF4B40324B19B6575A35C01D0DF5AA09BF4B400E54B52E675A35C030E12934FABE4B408F3F68CC715A35C012820632FABE4B4009F11FCD715A35C091FB9209F9BE4B40850BDE26735A35C0ACDBDD77EBBE4B4001C328EF7D5A35C04C4BC02BDDBE4B406C0D11A5845A35C0E5A7BC7DCEBE4B4096230C1F875A35C0E06FB3C8BFBE4B40296EC54D855A35C018EE14C8B4BE4B4097903DA9805A35C013C5B2DFB3BE4B40F63CE5C8805A35C0C9336878A5BE4B40E7E9E29E7E5A35C0602AEA6897BE4B4010F52B61785A35C0880211DE91BE4B4052A6452B745A35C0F8918D6C90BE4B40799ADE9A745A35C0FDC0425C8CBE4B40887132AC795A35C000E2F9158ABE4B4096AF93107C5A35C08EFACCD77BBE4B4087899343885A35C0CACDECB86CBE4B40D53311E78F5A35C0F1D1F0E065BE4B40B9A3A229915A35C029ED90C45DBE4B40282DD51DA25A35C09DC9BB0552BE4B4073B062EDB35A35C062765FEA44BE4B408E74BF03C25A35C0C7DEC2C736BE4B4093484305CC5A35C071F9DDF927BE4B40341CD4B0D15A35C0045203E118BE4B4060A08DE1D25A35C0BF9C04EA12BE4B40067EBE8FD15A35C09DB6E80010BE4B40419271E8D25A35C0FD14A1F801BE4B40DB6CD85DD55A35C0C9A93FE6F3BD4B4026744BE6D35A35C05C7C6A19E6BD4B405476188ACE5A35C0D1D73DE0D8BD4B40916B9667C55A35C08E1C9285CCBD4B4030B279B2B85A35C008F80650C7BD4B408174095AB25A35C02E2A260DC4BD4B40FE5D45C4AD5A35C072F93009C1BD4B4047DF122AAB5A35C010A4383CBFBD4B405D35DA42A95A35C01F60CB99B2BD4B4027E0D744995A35C0A3D7997FA7BD4B40C2721CB0855A35C03EBF30379EBD4B40312261066F5A35C0953549AC99BD4B404322F3335F5A35C088C6CB257DBD4B400550DEF8335A35C0030C015062BD4B4036C5B2860C5A35C02C569FBF54BD4B40B3A9185DFE5935C06BE5EAA633BD4B40FC90D1C7E55935C0DB07BC3028BD4B40B0F3B28DDB5935C0534D9E8F1DBD4B40FA6A5E8CCE5935C005E162F413BD4B40EFD68EFFBE5935C03EC5FE4F0DBD4B40ED96A5B2B25935C03832D6690BBD4B4070AB4878B05935C0D7CD9C8700BD4B40BF89C5E69E5935C0CC772E30F7BC4B40EFF4F2738A5935C05593FE26F5BC4B4089C13451845935C076B74BCFEEBC4B40E4FDC680885935C00103EA58DFBC4B405DC4C28F8D5935C0F7E8C9B8C4BC4B40F937C40F925935C0C31E452FB2BC4B401DEDB4C8915935C0763A1EEF90BC4B408652A8288B5935C0B112ACCB89BC4B4064C62138895935C0165BE7896CBC4B404E4970207F5935C0DBDE02BA4EBC4B4074E91983785935C0DC810AB034BC4B400916192D7A5935C08F28985629BC4B40D1156BA0795935C03EA4673600BC4B404E475D00735935C07AAA308DF0BB4B404FBA44F96D5935C0C97C138FE1BB4B4060485E1D645935C05F6FF72EBFBB4B401C5A3B3D475935C08153A36DB9BB4B40D50DC0F5415935C01ECE8B3B8FBB4B40EE2AEFE1175935C0CB52DD5970BB4B40D24F6D10FD5835C0A7DF40CE6ABB4B4056C30AD4F75835C0024711EB3FBB4B406380C4F9CB5835C0A517432C33BB4B40D37EBCD2C35835C0A50F533102BB4B4003292978B45835C019B97516F3BA4B402D310C4DAD5835C0A58C3D56B8BA4B40DE9BDA8C875835C0999CCBBFACBA4B40A81B76747E5835C018ECA47F78BA4B40C0103D944D5835C00CE88B8F6BBA4B40AC7F42F43E5835C0FF44190760BA4B40D81F9DA92C5835C0E81C1E272FBA4B40687B50A9D15735C0CBBBCD0926BA4B40BFC08CFFBD5735C010FF868E1EBA4B40C99E730CA85735C0B94C7BDE18BA4B40F3D9E148905735C0002CFB1815BA4B40D07FB137775735C064E9C95213BA4B408E59E9625D5735C04758AB9513BA4B406FE2C458435735C03A03645517BA4B40FB29F55DF75635C00A97D6720FBA4B402C0B9F1ADF5635C05870EBA009BA4B405B1CB5F6C35635C0EB34DE4306BA4B4086E6C566A75635C0D84C331706BA4B40BA9B5E27A15635C0DE808B9A04BA4B407FC702C9965635C0CAFA890903BA4B407F72BE1F7B5635C040CBC45203BA4B40D9CFE8A3705635C0071E8835FDB94B40E6174A3E6B5635C07BA67A38F2B94B405ABB67C95D5635C0DB637454E8B94B40EA14349D4D5635C01FBF1B77E3B94B4035A51F79445635C0FA3A78C6DBB94B4000E55421345635C047866170D5B94B401758E5DE245635C0DD96CE14CCB94B40007A9A7C095635C064948F8BC5B94B40BA25575DEB5535C06C6ABB03C3B94B409F4A31BBDB5535C0E9AA261EC0B94B40F239FE28C45535C021880CF5BEB94B40945EEF11AC5535C030D6B3BCBEB94B4056A8AB37995535C0FA598857BFB94B4034598CEE805535C044E7899AC0B94B402E4FD033745535C0F9485ACCBBB94B40DA092D0B765535C040943363ACB94B404015062A775535C0CE8776159DB94B408E290296735535C082EDE44A8EB94B40D2EC63676B5535C0FF12C76780B94B40EDC2A4D55E5535C01DCC43CA73B94B402F9FFC354E5535C0D20EE2C768B94B405A2720F9395535C0EC1245AB5FB94B40B77844A8225535C0D0CC0C745EB94B40F69E63F21E5535C0F70E989C57B94B409B0D0FC5055535C034ABCEFC52B94B407ACBCBE0EA5435C0E545A6B250B94B40B2AADBF3CE5435C038B2EDCB50B94B40A933B7CCB35435C094E2D7C54EB94B40C56A4B67B35435C046E6ECD44DB94B404480145BB35435C02810C89B3EB94B402DDAD443B05435C0FA1B7B893AB94B40FF799ECEAE5435C0DFEFAECE37B94B406B4F56E3AD5435C0B1ADFF3333B94B404B85F342AE5435C080B377DC31B94B400AEB5221AE5435C08EFD6D182FB94B402059A344AD5435C0AF9E57492DB94B4082FF7DBEAE5435C08579FF111DB94B40BA9FE3D3B85435C09AA1C4260CB94B4015CF834ABD5435C0C305613D0BB94B40AAF83860BD5435C00787839809B94B401829A7EABD5435C085E82355F9B84B407EC82897C05435C01E0696FEEDB84B40EBED109EC05435C00D21E734E4B84B40DB66EDB0BF5435C038CDB900D8B84B408F7F8E58BD5435C0E0568A18D6B84B4061FE5EF1BC5435C05A7CFAA9CEB84B40778DA13ABB5435C08B00EF91CEB84B4019ACEC3CBB5435C0335EDCD9BAB84B4027BF2442B95435C0E8A9B706AFB84B402DE6EABAB55435C05ECC479EA0B84B409752843BAF5435C0EB9063BA97B84B408552F054A85435C0323ECBFA96B84B4074FB9225A85435C0B3F4A1C08CB84B40030B8D77A35435C0F383005287B84B4089B77F93A35435C058937AB286B84B4024C8B895A35435C012B4E41281B84B40C429E9A0A35435C0C0EABD6472B84B401D4CAD99A15435C0241452846EB84B40E0810F7EA05435C0CC39A8FC6DB84B40CDCA8D56A05435C0F6F0E0B16AB84B4072608C5C9F5435C05087348E66B84B40FB7C6F449F5435C0D6C50D4764B84B40BA0549B79E5435C0E4ADB33759B84B4053857FC69A5435C021E76E8253B84B406CEA2063975435C0D1248BEE52B84B4064988635975435C0EB837FAE4EB84B4023430BB6955435C0FD0EE31644B84B404E84BC81915435C0A948D6EF40B84B403D725326905435C001075BCE3DB84B401F9437B28E5435C0C8DBC3DE38B84B40CD5C9C6A8D5435C0C0F2DFAD24B84B40D4003EDA835435C0445E58FB1CB84B405A2510767E5435C0ADB4E91F0FB84B402CAA6B8F755435C020E6B3E802B84B40EF5E1E776E5435C05829933AFDB74B4039575A026C5435C047807A84F7B74B40FD6A038B6A5435C022F2F5D4F4B74B4072FEAAC7695435C08DC5F78FDFB74B40AFC89D26635435C06C961215D6B74B4061AF2D355F5435C0CFF0FEE7D1B74B402611C8DC605435C0EC1A7706C4B74B40653DB876645435C0018E97A8B8B74B40E310881D665435C0CD73AE44ADB74B402ADB7632655435C094768D349DB74B40D7D08414625435C03C05D13699B74B40353A1525615435C0AAE3C7D089B74B40D23E9BE95C5435C0E04A63D77CB74B4001A27AB05A5435C002EDEBF16CB74B40F7557760555435C02E242DF860B74B40425E2F5D4F5435C0737E712B5EB74B404DC81DDF4D5435C09647CE1D55B74B40B8D139C3485435C0C5DB18C249B74B401E85CD38445435C0522D70D143B74B400FB7F977415435C0268A071E36B74B4093A6293B3A5435C009FC6CD035B74B40724B382C3A5435C0C932E6ED32B74B4070D88A88395435C0E8FDB00A31B74B40D8A1590F395435C08E7C672630B74B406AAF160B395435C04A92199823B74B40F53BB13D375435C0FA3570E522B74B40D3C0810D375435C0E26962B215B74B408578F0FE395435C03AE2456310B74B40665BBA583A5435C0D55AD7D502B74B40CB82226C395435C0A88DF57DF5B64B405C25F4E1345435C08CC2FC89EDB64B400689CE0E315435C0966A8919E5B64B40BE1825342C5435C0ADF9381FD2B64B408C1570721F5435C0109C6366CEB64B40E92C3EC71C5435C0AAC4ED55C5B64B409568BEDD155435C0AD47FCE7C4B64B4054EB5089155435C0DAE4BA8DBFB64B40ED51B765115435C0D53B73CFB6B64B40D9DBA4FB0A5435C08979484EB6B64B40F637159C0A5435C05CA9B0B6A9B64B40BC452B37015435C0E7DF422C9FB64B40D0368D7DF95335C08E9E7F0B9BB64B40E4F1A440F65335C0FB9CD02990B64B40280B4125ED5335C0292B86E589B64B4080A8695FE75335C008F1D7CE89B64B4019C98948E75335C0C251B70381B64B40B309B303E25335C042C029B079B64B40FC4D2CFEDC5335C012A002B172B64B40B8975293D75335C0F67812ED71B64B40BE73C1F7D65335C081AEC7AB6CB64B40DAC7755ED45335C0808430985FB64B40C5736FF2CB5335C0309AE6AE56B64B40D3827ACEC45335C0467FCE4C4EB64B40E02A7927BD5335C0370A18E243B64B4094D2286DB25335C051E8284D3CB64B40A6D75CB2AB5335C0DD1E87983AB64B40ACE4BA24AA5335C0A91DA82138B64B40312C83D7A75335C01BFBF0B22EB64B403D6935BA9D5335C0F53EB4242DB64B4080DD58D49B5335C080E14FAB25B64B409ADA07C9945335C0F940D5FC21B64B40740C8C1F915335C091505FBE1DB64B405DAFC2AC8C5335C09E28EF0014B64B407C65F7D4805335C0419C81350CB64B403FF130FB795335C0BFFC9EB009B64B406A51AE49775335C0ACC8F80500B64B400945D76E6B5335C04796D9AFFEB54B40DCD80591695335C07156537EFDB54B408AD59666685335C0832EFF5FF1B54B40FD2AAAAC595335C0B8373AF4E9B54B403B103CA0515335C06CF4E13BE9B54B40270992C9505335C0F7A2F6B7E7B54B4013089CB44F5335C02E665694DCB54B405769562A465335C0517A9E30D2B54B404E8040033A5335C02C477520CFB54B40E95335E4355335C0F1039BF0CDB54B40211E5085345335C0921E2642C9B54B401DB2F2BF2E5335C0EED4743DC5B54B40A7598978295335C02F08CB22C3B54B40DA25A07C265335C0E2E27084BDB54B404D0E23CE1F5335C0E1034497ADB54B40A1D0DD590E5335C08B94BD32AAB54B406E4193730A5335C0330FBAAA99B54B409590FD8AF65235C085EAACC08BB54B40442578ECE65235C014CD29B889B54B405BEB5793E45235C0FA2E25AC76B54B409CBCFCF3CD5235C08E144D606BB54B40F4FE8237BE5235C046D89FA263B54B40AC4D27A3AF5235C073C3CD1662B54B402BF7A010AD5235C04BCF17E460B54B401F531D89AB5235C0D863FCBF5BB54B407D42ECFEA55235C0A623319457B54B40AFAA9B3AA15235C0118AF5B242B54B406F82CEF5875235C07354F1603CB54B4056417D55805235C087956BFB35B54B40B3AC68E5775235C049C6EC362BB54B40151D9468685235C0A1564D1E2AB54B40ED36C7CE665235C04D79275A19B54B4022B679F34D5235C04115D62210B54B408A2FCDCA405235C023B660200CB54B4016F0D1BC3A5235C0BD7DCD4400B54B40B69746D0275235C07816AE7FFEB44B4061A7C3EA245235C0F72C762DF6B44B4029868FF4165235C023AA8061F3B44B40B8129A11125235C0AA44D17BEBB44B40E0E48BB3035235C04E7C3D6BE6B44B4047D41A6EFB5135C0DCE225F5DCB44B405C90BA9EE95135C063BA371BD2B44B40F44CAA12D25135C0A2FD5AA1C6B44B401204CE79B95135C052AAB6FFC3B44B40D6E44B95B35135C052AAEEF0BBB44B4055248AB9A05135C0F85E69F0B5B44B40980C1EFE905135C006216887AEB44B4060B458257B5135C01CBBDEAFABB44B402EA2EEE4735135C07A44BD3CA2B44B40C9BBD89A635135C072F402B397B44B405F348E314E5135C0372A8FBA96B44B400C6E39DD4B5135C04F1BEC828CB44B403F66D78A375135C070568DE988B44B40756FBA61315135C09C99E6D47DB44B4055048A2F215135C0BDFA65E573B44B40609E006C105135C06C1F6F6370B44B405CC9C59A095135C055A244756CB44B40436F2D89025135C04E4F12A369B44B4032C5653EFD5035C02542744466B44B40803D8CA6F65035C07630B8485EB44B40F46946D3E45035C0550FF49C57B44B40230B5432D15035C083FAE55B53B44B40C37D6A15C05035C0E0F117A64FB44B400B547E1AB75035C0927F6F1548B44B4023F2CCC6A15035C058CF2F873FB44B402814C869855035C0B3F98EF23BB44B40E95E414D785035C046B9BFEA37B44B40BBB322E6675035C01F75E3DA31B44B40410D1922475035C0CF94814E2FB44B409AF7C1C8245035C084B6FFE72EB44B404C6EF132125035C0900DD6CA2DB44B40EA03F25DF14F35C07114E8C32CB44B40099EC23DD94F35C0482A0EA22CB44B40FD5B71AFD54F35C03FE0AA562CB44B40DD38626DCC4F35C0C0937A322CB44B40AD54A37ACA4F35C069480C3A24B44B4053EF8379B24F35C056196D0C24B44B40E4207B02B24F35C00957050F1BB44B40282EA0719C4F35C0439F7BF319B44B40773DCCBB994F35C084DB149415B44B40C15F56D18E4F35C00F4E0BDD12B44B405303FFAD874F35C08E6CCBE50FB44B400AEED1757F4F35C0A7E03B0407B44B40E214CBB2604F35C030CE4F7C05B44B40A43774DC594F35C077C3574303B44B408FC79C30504F35C0F3B8359DFEB34B40EFC84FD9364F35C0A0B88E37FCB34B40A5CFEBA8254F35C00B9C80FAF9B34B407BC0E6811A4F35C06C770559F7B34B40DCCFAD95164F35C03E415A1EF7B34B404C8DEB3D164F35C0A3278641F0B34B40BFB8A3F10B4F35C0F870F23DE8B34B40643DE2AA004F35C025291E98E1B34B405A5B3A65F64E35C080D2645AD9B34B4048BE376FE84E35C023518171CFB34B40901A25E3D44E35C0AAE958A0C7B34B40A4DB7BE9C24E35C0F7551D06C2B34B40943F39F4B64E35C07A6BFDC7BDB34B40631A454AAE4E35C08B2A2F6AB8B34B4060A3DEA1A44E35C05E29ACC7B1B34B405C857505994E35C02B8CF37EA7B34B403F0DD8EB894E35C084067A2FA5B34B40F20CF96A864E35C0DDED2A4F9EB34B408C5E09A77B4E35C0C3D6A38486B34B4010536B3C584E35C01041022481B34B406C32D448504E35C0BD95F31678B34B40A6F0EE08414E35C0E098AB7374B34B40A04046103A4E35C0EEDD5F2674B34B4034C286AB394E35C0E78FCE116DB34B40213A437B2F4E35C06DE097D263B34B40C07CCCD3204E35C0F603711E57B34B409C0D69100D4E35C085E5C69A4FB34B4049A4B306004E35C0092BBA6448B34B404C5A9A15F24D35C026B627943FB34B40D787765DDE4D35C0F91D6E6038B34B4067933777C84D35C0ADEAC6ED35B34B402B8E79D5BD4D35C0D420AF1F34B34B407918EE46BA4D35C0ADD9324A2FB34B405717A581B14D35C0EC0AB3D228B34B40277C97AEA64D35C0BC5A07E61EB34B40139A4054964D35C0B29F795E19B34B4057C607738C4D35C0368B8D7E17B34B401FCE7CD1884D35C0E9E7F30815B34B4097934D0B854D35C0AD455EA00BB34B404AD9A479744D35C08692CD8103B34B401F4BADA9614D35C0F6B38CFFFAB24B40D62EE0F04A4D35C0D80FA465F2B24B402008E0D22E4D35C0F136B001F2B24B4011F389392D4D35C0643FE538EBB24B40F01D1D4A204D35C0BE38B02BE8B24B4054C0EF67194D35C0524F0944E7B24B4073B62336174D35C0F3EFDB64E2B24B40B03899170F4D35C08100CB91DEB24B403CA4AC34084D35C0EBD0CA83DDB24B4094F3BE77064D35C04D4EBB3EDAB24B40AA5810CB014D35C0879F8127CEB24B404FB1802FED4C35C0884D0722C4B24B4014B47EFDD44C35C04FE2EC8EC1B24B40D9601786CD4C35C008A9872DBBB24B40FB56E9D6B74C35C051FF0678B6B24B409250D4B3A04C35C06620B70BB6B24B40A1D44D3C9D4C35C063890560B5B24B4084B03F3C9B4C35C05142AC56B1B24B405DB133F68D4C35C0710B3761B0B24B406388A7778A4C35C0D7D9FC78AFB24B40769951D1874C35C0F3627753ABB24B408320DC757A4C35C03C38DC9BA8B24B4060876FC2704C35C0D6552191A7B24B4033BB7BED6C4C35C02D1F8DE6A1B24B4082101973574C35C0C79ABEA6A0B24B4035055D3B524C35C0FF7382AA9DB24B4039D647214D4C35C075D03DE996B24B407ECEA9E8424C35C0F3812CA28EB24B402FB19443394C35C09DF3E91986B24B40D05F1F03314C35C083C8F90983B24B40498574EA2D4C35C0C28282F67AB24B405CBB2963254C35C01691CA9679B24B403095FB32244C35C0301E06F571B24B40E91822C71B4C35C0A6C48E9570B24B402C5E35CD1A4C35C07ACF228962B24B40B6B4D23F0E4C35C03E43003C58B24B40AE622F03034C35C0208D8DD052B24B401F7EB5A1FC4B35C031F30D254FB24B407BF2D1FAF74B35C0D5AD14C547B24B4034A0F29DED4B35C05441B3B843B24B40E3E24F54E74B35C0EB4BEA573FB24B403BB2DB5DE24B35C0EAC38AF139B24B405F28E1C3DB4B35C0D9397B4937B24B404C493B45D84B35C04F3A729F26B24B402D23C34AC64B35C00D2870EB1FB24B4020B8088FBD4B35C07EAFC6CA1CB24B40F8980A99BA4B35C073E90C1E18B24B402224ADE1B54B35C05BE288A314B24B405CFD49F7B24B35C06E4267F813B24B40ABB75C66B24B35C03F56480E11B24B40D8BEF5E7AF4B35C0688F46DC0DB24B4067BDD1BDAD4B35C0AA8033CE02B24B4004FF3DB9A44B35C021B07B7DFFB14B408A5AD688A14B35C0B0DDF3AFF7B14B40B454E95D9A4B35C0DD2F5AABF5B14B405D4AF374984B35C058633864F0B14B40D9F8F34F934B35C055C65ED5E7B14B40EA00FBDE894B35C0B1029991E2B14B409C09E857834B35C01A699FE8DCB14B40D045A25B7C4B35C0D6C7EF29D3B14B40A9CBA9906E4B35C0C5AFEFEBD2B14B40F2D8AB1E6E4B35C086C6F0DAD1B14B4051B0E2D56C4B35C004DC7F2BCEB14B40856BD66A684B35C073648EF7C3B14B40BDB1C9055D4B35C0570C2E3BBBB14B409F9B76FC504B35C02BD8228BA3B14B40E67FF519344B35C064EF0EFCA1B14B40E7E3BB28324B35C032CFB6B49AB14B40CCF10AE5284B35C094F8DA7886B14B40EC7DCD35134B35C07430DC6570B14B401F4793BC014B35C0E89844DB60B14B40F18465C1F64A35C0F5698C2C5BB14B4036FE1C57F24A35C0D87E6B7C56B14B40971BD95BEE4A35C07B03F64D52B14B402F88E54CEC4A35C094B7E9504BB14B40618F7D50E84A35C0AEE3D92641B14B40A37854B4E14A35C0041903C236B14B40ED8A3E99DB4A35C09ACF15292AB14B40F86B2D48D24A35C0A99F325D24B14B405FA91AE8CE4A35C03D1859291DB14B40E385771BCA4A35C05598087219B14B40EBCF494FC74A35C0137CC84419B14B40C1412E39C74A35C0252961D816B14B40B7218BF9C54A35C068F15B2911B14B40220FD9E4C24A35C0820D31DC0BB14B40845073B3BF4A35C0E0DA228505B14B40E5704A7EBB4A35C05D7363B000B14B40909E7BB9B84A35C033B9071AF7B04B408DD853AAB44A35C03974DEF8F5B04B403D40682CB44A35C0E8B3407EE9B04B404B3B9196AE4A35C0367ADB8DE7B04B40243EFDADAD4A35C0AE5BBDB5E4B04B40CCF68049AC4A35C008253B34DBB04B40CA1E949DA64A35C0011897D6D8B04B40056C30F1A44A35C01AD85C01D6B04B40F70ECDD4A24A35C04C774879CCB04B40D677A6A19C4A35C04EE75311C2B04B4054409088944A35C0E5B4326EB7B04B40A002E8D38A4A35C030E5D019ABB04B401AEDCD557D4A35C0D0CA32FE9FB04B40F063DE856C4A35C05519D2389FB04B40AD67F1296B4A35C0ED6839B19BB04B408258FE0E684A35C02939F2D78EB04B40F4C2E9B45D4A35C02BF7F46F8EB04B40DD40286E5D4A35C0C6BA89B88AB04B40F196F3E15B4A35C062FF7B7879B04B4090A2465D514A35C0B786401171B04B4092C277A1484A35C0F65E859E70B04B402F3E7373484A35C0C3B6EB3A65B04B40E58FDB5A404A35C0F37248945BB04B4054F2DFD53B4A35C0B6C295E850B04B401772151A354A35C0BE6071AC45B04B40293596422F4A35C02961295B31B04B402EDD5B82274A35C068F6B4F92DB04B40D76435E8264A35C0E2A8F42B1FB04B40056F6E06224A35C062F05CF710B04B40240CE0D0184A35C051A003970DB04B4015D0150C164A35C0EEC21BF408B04B4000F6B865134A35C0CCDA8591FDAF4B40D9864D41114A35C0C1F40B17F9AF4B407117BC35104A35C03DA7BA16E5AF4B40AE06D1A10A4A35C09C1098D9DBAF4B40D3C11D06074A35C003D92C76DAAF4B40DA0A5AE2064A35C00AF8F3B3CEAF4B40B2F5FE4F044A35C0F94A0A35C3AF4B4057CD1103FF4935C073FA36C2C2AF4B4009CB9CBFFE4935C09DFF8FE5C0AF4B400DEAE255FE4935C0FD9C027FAFAF4B406D755BBAFA4935C078B5D744A5AF4B407A7CA689F74935C08292D3289BAF4B40CDE69E4EF34935C093C69EC390AF4B404251EA40EF4935C093E8389182AF4B40F0C9A40AEA4935C0B80112017FAF4B4017607A99E84935C0CAA576116FAF4B4014C9B88BE14935C028766EF461AF4B40A797581DDD4935C0E62ECCBB61AF4B404D22140ADD4935C0B4BC716A54AF4B40A2CB567AD84935C0B194316946AF4B409E267092D14935C04BC5376142AF4B40B65EC3F3CE4935C077AFF0C032AF4B406F3DCCCDC64935C02A052A1B2CAF4B4042FB74D6C24935C0B28413431EAF4B40D4E22783B94935C0FE7850E517AF4B40D76E4F6FB44935C038291EC814AF4B403A2B4B69B24935C0B7B2C7BC13AF4B40EA404AB8B14935C019372C990CAF4B4099D4A9E7AC4935C0EF97FF8D09AF4B40CF53C553AA4935C01569C89E08AF4B40325AA210AA4935C09E558F02FAAE4B40466ED744A14935C0DADC1E9AF1AE4B40F9F37334994935C088581226EDAE4B407E2D071B974935C01FEE1DF8DEAE4B40D11ADC1B8D4935C0124CB911DBAE4B40D96966B88A4935C050B1BA82D0AE4B4022550C0F824935C0EE374E5EC7AE4B4070C122157D4935C06C4AD884BDAE4B40DA29445B7B4935C03848CA5DB9AE4B40241FB8297A4935C0489EEB06ADAE4B40601ABDF4744935C0BC72DED6A6AE4B40E88CCC4A754935C0EE15E71698AE4B40F872A2BA714935C06FD19FD189AE4B40E75806E2694935C0499C727388AE4B4015E008AB684935C06FFAF6AA84AE4B400FBD087A674935C09F40A1197BAE4B4026A1605A664935C0ADF1935172AE4B4002DD11D1634935C0BA9D260070AE4B405D7FB989634935C0100CD9CD62AE4B4054858531604935C0D5B9B1E55DAE4B40A0FCFA985D4935C0FD03582057AE4B40412C58AD5C4935C0FEF7160D50AE4B4029CAD7495A4935C078CAE1884EAE4B4078C97D505A4935C063BD87F54DAE4B40DDB0A28C5A4935C07C7BE0723CAE4B40521A49825B4935C01D3230B935AE4B40889885B55A4935C0A3BFAE6C29AE4B403A82BFB8574935C0E90DC14921AE4B40025528B8544935C02D6FC88B16AE4B401F4C8082524935C0895D8EF70BAE4B40F14780D74E4935C066CFFC4308AE4B40CC7487B64E4935C05AF85CD7FAAD4B40D3B9BF724C4935C053258422FAAD4B40D579B53B4C4935C0D5127705F8AD4B40554DEEE84B4935C0E1CAC12CDFAD4B40F61F92774B4935C00CF57A01D7AD4B406273CCA84A4935C02C8E13CECBAD4B404331D3A3484935C0A6CEE716C4AD4B403E5F72A3484935C03F7A4B2DBAAD4B40E98569A9474935C0E1373AB2B4AD4B4036F76C94464935C078D70CA9ACAD4B40B1E36AE7454935C0BAEDCD209FAD4B40D90A9E03414935C0319B9A2298AD4B407FD0487B3D4935C02A9D215894AD4B409DC21D453C4935C0ED5C453F8FAD4B40FA2FB0323B4935C021714B098EAD4B409BF7B4ED3A4935C0B2E5A51686AD4B40702F0210394935C000B2EAA87BAD4B407AF0587E354935C0D5FB570955AD4B400FC91A0A244935C0F03F934C4BAD4B40656B88961E4935C08D1BE2E044AD4B400F5A1D471A4935C01925766B3DAD4B40DAA8273E164935C054B2934832AD4B4069A09B28114935C0D22448E02BAD4B407EA643C80D4935C0027759B623AD4B40FC25EAE4084935C0974028741CAD4B40206DBBBF054935C0B32355AD16AD4B403AB81CE2024935C00E642F2010AD4B40DE321037FF4835C0AF473E7706AD4B409E524758F84835C0B1E1172B01AD4B40C2855BBAFB4835C080271E56F2AC4B407FDA02C1024935C0B2653318E3AC4B40C2FC9936054935C0807619D7D3AC4B4003DCB50A034935C0DDDF145FC9AC4B402197DD4AFE4835C0FF65A43FC8AC4B40EC5F6961FE4835C04C08A1D2B5AC4B40A6501474FC4835C0EE354839AFAC4B40589BCF8AFA4835C054AFEE8FA8AC4B40BF9F0327F84835C05F4D9E519FAC4B40028F3F30F44835C02D862EBE99AC4B40ED72CD94F04835C0ADFD87FE97AC4B40059DD463F04835C0CBE42ED689AC4B406B229FB2EA4835C01F0E380789AC4B40C0DFB11EEA4835C05B6C6FC183AC4B407D5DBCD6E94835C06DAC81BE75AC4B40708A0917E54835C0C00CFB6271AC4B40913B644CE24835C0F13C5E556DAC4B40B5EDC793E24835C0F1E3D6D663AC4B40FF03179AE04835C0719736F962AC4B40778E07AAE04835C0DD0F114852AC4B409C4BB542DC4835C012E4CBAB4DAC4B40292B776AD94835C05B338FC746AC4B403E275240D64835C01B086F673BAC4B408751D400CE4835C0BF6A8ED339AC4B4044AC68A2CC4835C07567412438AC4B400C5713D2CA4835C08E09EC1B2FAC4B40636CF753C84835C0ED0702781FAC4B400A41FD99BE4835C00118EA2F1EAC4B409F3F9290BD4835C0F9CDE5DA19AC4B40094681E1BD4835C0BE3316880AAC4B40C3B6E64ABA4835C03F9507E507AC4B40CCA5C5D4B84835C062735029FBAB4B40BEFE68E6C34835C0116EF2F6ECAB4B40319C3B80CB4835C0E1BE6C4FDEAB4B4027B4E0DFCE4835C08C72E08DCFAB4B40ACD15DF0CD4835C0912C35B4CCAB4B4012EF91E9CC4835C0DC986F52CBAB4B40871144A4CD4835C089E9B078C8AB4B40B6B4D14CCE4835C0DF8D15C5BCAB4B409DE204B7D24835C0D3FDC729AEAB4B40EA9086ECD34835C0F1919BA19FAB4B40FF96C4E7D04835C02D8A2E8591AB4B406C8627BBC94835C0731A94E18BAB4B40D689D504C54835C0822250A087AB4B406F0857EDC34835C01A35D62E84AB4B40F5AEBE6EC24835C005D9A18A75AB4B406EC9DCADB94835C09C2D0DD967AB4B40B64F6C9AAC4835C0AC2F09B15CAB4B40395BD43D9D4835C0159B47F650AB4B40D51E1CDB954835C0712C7E064FAB4B403C10ED51944835C0D67C73CD41AB4B400A2EE872874835C0400EE5D135AB4B40A6F6C0D9764835C0985DB45F2BAB4B40163B94EF624835C0322BE19729AB4B4094317C415E4835C0188218A022AB4B405E8D3A50514835C0DA81F49D1AAB4B40756333933D4835C089C3E3C414AB4B4045E383FF294835C08588B2DD0AAB4B409BD4F8E1184835C075BFCC3102AB4B40DC866DCB044835C0D74C8D53FEAA4B40CD73F08CF84735C01DB386CCF4AA4B4036732661F04735C0FCEBEAB2F0AA4B40003C6E15EC4735C09F859BAEEEAA4B4051A18697E94735C0D1FCFC82ECAA4B4058CB4F77E84735C0AE5075CCDDAA4B40B3740A6ADB4735C0F1E7F4BDD4AA4B40BF413272CF4735C07B7576F3D1AA4B40200DD218CE4735C02CF23134C5AA4B40D0805209C44735C05F6DC75FB9AA4B4061F6889AB64735C0EE7DC5B6AEAA4B40B2EDC315A64735C0F78C5873A5AA4B4093EA28D5924735C02633A979A2AA4B40C76890A88B4735C0955F93E99CAA4B4073AC75997B4735C05A77D1E194AA4B40D34BFC316F4735C0A4CDE0498BAA4B40435D36145B4735C069E1E0DE84AA4B4007BC509C484735C0554271E883AA4B409DECAB8C464735C0D3EB61D37AAA4B40362BBD7F3A4735C0F0D5B78870AA4B406A8C8FE7274735C07FB520DA67AA4B40A2D3769E124735C02AB736FA60AA4B40D1848420FB4635C06BDE97DE5DAA4B40A0DDAE33EB4635C08DB7ED5C5AAA4B40F410CD41E54635C07CC450893AAA4B4046FE2F30A44635C0F9F21FDB35AA4B400A5875DA994635C0A50A5AEE1FAA4B403ADBF583654635C0546733C817AA4B404C740C9B4E4635C0E3BCD09E11AA4B40FB450E9C354635C0A0DED72E04AA4B405A0451B6F14535C0B5DEF27D01AA4B40B9AB31E1E14535C0EE719D31F7A94B402570A0C9994535C0FF514186ECA94B40547F2611624535C0E482E0D7E1A94B40900F0368314535C070EE3C1FD5A94B40261C5910014535C02AA6BB06CEA94B40AFAFC66EDC4435C06A291720C4A94B40738D5C0C904435C0E8881ED7C1A94B40E34B5E49764435C084770703BFA94B40A8CA894A3C4435C08F963BE2BEA94B4011F41FD71E4435C02850A4B6C1A94B4032E7491DD84335C0EB078734C4A94B405DDC3EDDBA4335C0D3EA3B47CBA94B40D47AD91C854335C08CF4F527D0A94B40453634CC694335C0A0104B53D7A94B40EEA40C52504335C0C57EEF98E0A94B405A9AFF59394335C02BCF6DBAEBA94B400F80C47E254335C019A5CB6CF8A94B40BC481B46154335C0E6C1825A06AA4B40EB9E471D094335C0A40EC12515AA4B4089FF3056014335C07328EF3C19AA4B406066587B004335C00D93ABFF18AA4B4034EF0E0DFC4235C013C877FF18AA4B40C1868F02FC4235C0F8C6A4CE18AA4B402DA45585F94235C0E3DEA15F18AA4B4016B08260F54235C06B1A254C18AA4B4087BCF6C7F44235C047D7301918AA4B40931CC02DF34235C0DA12C0CE17AA4B408C1CAA6BF14235C0AE8638A317AA4B40000F9961F04235C033BBD85D14AA4B40FFBED42ADC4235C0599FD8A713AA4B4001A2B4C4D84235C0BF7B299110AA4B403EF53321CE4235C020D1A1E80BAA4B4054FC3665C04235C0B30E8E9F0BAA4B40A0D6688CBF4235C0FE2C719D04AA4B408D5D3AA0AA4235C0E66284EA03AA4B402AFCA481A84235C0F82EAD0E02AA4B407F3DC7C7A24235C00F439FFF01AA4B405150C1A1A24235C0CF9CD1C5FEA94B40237D3CC09C4235C044D850B7F8A94B404725E8BE914235C0F3C6AE7DF5A94B4094B700988B4235C0E70600D2F0A94B409A20923D824235C0DFAB6EF8E6A94B40B7F8F3BC734235C04C9DAD61E0A94B40BD81AF2A6A4235C02F9FBD50DCA94B4016F7E9E9634235C053421EB9CCA94B40CD1EB78B4A4235C04CECE5A7B9A94B40CFF2FEFA324235C0935F12A1B6A94B409CD171142F4235C02324D32EADA94B40CA44BA64224235C0875EA500A7A94B40F6884E6B1D4235C0B4142C3B9AA94B40198D5D3A0E4235C0FCF992E98EA94B405F824B6BFB4135C0953344458BA94B4069B6ED50F44135C0D7C8CDA585A94B40A42005BCE94135C097AE46A681A94B4026B0E84EE34135C008D0C8FB7AA94B40B587537FD74135C03333CBEA72A94B40F4BC32C0C74135C0A6F974996DA94B40794E83DFBB4135C0A362E45662A94B4019BA38F1AE4135C0F16A5FAF56A94B40A7DF58AAA14135C0A61C637350A94B40A51745EA994135C009425E763FA94B4062F448EF824135C080E697B139A94B406EF891637B4135C05429FE0434A94B404CF110DA744135C0E1924FBD27A94B40503CC0566A4135C0A455364825A94B402F69D418684135C0B74B0D0917A94B40CAF05DEB6C4135C0534D941B08A94B404BD87F806D4135C0169E192003A94B40E8AD6CF56C4135C06EE1A78AF5A84B409263DD9D694135C0BE246E5BE8A84B400F03A6A2624135C0A5E00BD9DBA84B40C1FA2B29584135C0F7804FEBD4A84B40586C0531514135C035465CAEC7A84B401FDCD813414135C00C04DC0DBCA84B40D952CD102D4135C0733D38F8B6A84B40AB112FB9224135C0E55D2189AEA84B40701E69E50E4135C06500B8B1A7A84B409AFEC004F94035C0D476B495A2A84B4047D27289E14035C00A149FE5A0A84B403A76B8DAD74035C09A9927429DA84B405944FD06BB4035C0928CFB399CA84B4043BE9F779D4035C09EDC61949DA84B40D8BBCC98844035C0A50668889DA84B404B72CAF3834035C0269AECB29CA84B404DA10F78674035C0ADD7EE4E9EA84B404331531A4B4035C03B8ACB51A2A84B40A80FFA952F4035C075C201A1A8A84B40B7AACCA0154035C0B440C468AFA84B4001829893024035C0A8C64466AEA84B403F67A5B1FF3F35C0054B677BACA84B4091D3D589FC3F35C04818FC4CABA84B40939AB18FFA3F35C0B29BBF2DA2A84B4050251009EB3F35C06452F71EA2A84B40EE8CFAEFEA3F35C0CBF1B09C96A84B400D0EDDDADA3F35C05864BAA98FA84B4098623625D03F35C051F8B9E88DA84B400654702BCD3F35C091658B7A8DA84B40013EA68DCC3F35C09F69DF7E8AA84B402984044DC93F35C0A5E5403580A84B404AC5EC52BC3F35C09C7FB68A7BA84B4006F5D390B53F35C0622CE9B57AA84B4003E54991B43F35C016E7542F7AA84B40EBBC04CAB33F35C0B102D43478A84B400AB0A09BB33F35C0AA447E4A6AA84B402E8ACF59AE3F35C0840B59F167A84B40DD2D341DAD3F35C0486545C75AA84B404DC6292AA43F35C074C134764EA84B40D31B7AAC973F35C089B7144343A84B406DDE0CEA873F35C063590C8541A84B405352CD0F853F35C0544F878A34A84B40D77A3A136B3F35C02D502EC133A84B403741D924693F35C01009521127A84B40EEA6CD66673F35C0F39A163023A84B40F1B7193C663F35C0E3B1734F1BA84B40224264B5623F35C057FF01750BA84B4004BCE86C5F3F35C08F66C7DCFBA74B40FBC340F7563F35C04D077844EDA74B40BA380DA3493F35C06435BD3BE5A74B403EFA57A0403F35C0E2EA377AE0A74B4026D5BEEC3A3F35C0F857502DDDA74B400C5A03B4363F35C000C743A8D2A74B406BFE1817273F35C088C3786BC9A74B4037AEC5D8143F35C0281D64A6C1A74B4028B3C356003F35C0552FF080BBA74B406F536FFAE93E35C039D9AF1AB7A74B40A679A936D23E35C0EE585C35B6A74B404810E6FACB3E35C01CBB95D4B3A74B40314BAE01B23E35C0BB2C2AB7ACA74B40D8D910E9AD3E35C06A7E0F669FA74B40AEEC31B5A13E35C010DBA94293A74B40831941C2913E35C05E03169988A74B405AEE40747E3E35C0FF432FAC7FA74B4039943E44683E35C03440ECB378A74B402CA95ABD4F3E35C0321400DC73A74B40FEC46079353E35C006BBAB5973A74B403899151D303E35C00FD821DE6DA74B408B19810F1B3E35C00272E67569A74B40B10EF5A7FF3D35C0F2F169B568A74B40A2BD9C1CF93D35C0CE2016D267A74B40E646AF6DEC3D35C0142829745AA74B40D684F6BFE63D35C03D97744F4DA74B40541C0C23DD3D35C0E51455BE48A74B403A32A609D93D35C0A59B1A413CA74B40E5FC228CCB3D35C08588690031A74B404F96E0A8BA3D35C0525FAD052FA74B401240869FB63D35C0A717350226A74B407F169377A83D35C013AF2C7721A74B40DED37936A03D35C05DCA90E417A74B40D0055FF28B3D35C066EA0A1310A74B40ABD21030753D35C0E8D3EC470AA74B40BFD0CFD65C3D35C0116006E5FFA64B407D5509465A3D35C09F9E9B0EF2A64B40A2E931B8523D35C0BB5D1D5CE5A64B40B5093AA3473D35C06962391EDFA64B406E5763F3493D35C00A57C7EAD1A64B40A5E8D2524B3D35C0F4522ABEC4A64B40925A553C493D35C0D3E622A1BDA64B40AC8CA929473D35C0DFD2093FB4A64B40C85D1982433D35C05F04BB17AFA64B40320D21FC403D35C0968A6A97A6A64B403A61DE503E3D35C02C9D049399A64B402F20BC7C363D35C0E95EF8F88DA64B4020C67DBB2D3D35C0228AE73587A64B40F19F80EC2A3D35C058E5180F7AA64B401D0B6472213D35C04C2F85CD6DA64B40A6B9816A143D35C0741F9FB662A64B40DD7DB01E043D35C034B160B75AA64B40D5984973F63C35C03DDF310557A64B40DCDEA716EF3C35C0960220AD56A64B4062D2C2D3EE3C35C0D04DE38C54A64B4093BAC4DAEC3C35C0B72ED87047A64B40C3B5DE1BDE3C35C0EDE8BD1545A64B40F2DD9BF2DA3C35C077BB7FFD3BA64B403C7D0512CD3C35C0218A3DE133A64B4080382A31BD3C35C0219A966432A64B401500FDE4B93C35C0173CEBF42FA64B40300CC790B33C35C010F6A91129A64B4077714266AD3C35C06C6746D621A64B401A703FF9A33C35C0C5A48F0417A64B40DB083F6F9D3C35C07FEBF03809A64B4083684A1A903C35C0A1658B9A05A64B402EB943DE8B3C35C06659C03903A64B400349F5FD883C35C0A107EF82FEA54B404EE2041A833C35C0373F200FFDA54B40776D0B40813C35C04EAD151DFAA54B406DFA826B7D3C35C0991BC35EF4A54B4025DBCEB5743C35C0FF7B3286EFA54B405A221248723C35C021A01FF9E1A54B40910D87FF663C35C0DD6A0687D5A54B40A7950CE3573C35C0E44B017ECAA54B4048137851453C35C056655023C1A54B401E4551BF2F3C35C0B07BCC33C0A54B40E349EB342D3C35C099CC417EB8A54B40313C391E143C35C04B134057B6A54B407CEAEE60093C35C0B5B689C0B1A54B408D1E6C5A063C35C0172FE4C9A4A54B408F345E45F93B35C0372D2A1099A54B4067F84F94E83B35C03A6C456F98A54B40A340DD87E73B35C0E7812A258EA54B402EA02E70D33B35C0D78316148DA54B40B2C28F90D03B35C0AA41483086A54B40AF552253C13B35C0267F49CE84A54B4019F7B515BD3B35C0F047312084A54B40AB8EBFD0BC3B35C044F5E70C76A54B40D37AE60FB53B35C050CA58C868A54B4097434D53A93B35C0B82F07A45CA54B40A0960FE3993B35C0D5688BEA51A54B403116071E873B35C000CF8C0B4DA54B40B636C8767B3B35C0ADD50F3745A54B40D5D26625713B35C065C675173AA54B40B7798D9C5C3B35C08C59953939A54B40A3AD31B95A3B35C022F491E530A54B40D20D349A453B35C0BB0142522AA54B40BF989A682E3B35C06F2F7AA425A54B40DD7F52A6153B35C066286FF622A54B40F5350DDEFB3A35C04D82379E22A54B40583FCB55ED3A35C0E51D841D20A54B40873F99B5DE3A35C0CA58AB7C1FA54B4084C21CCDD93A35C0519CD4181DA54B40CA57E79BBC3A35C00B1535311DA54B4032433204B03A35C0C7E41D5818A54B401B5D85A9A53A35C062D616A010A54B404D2F52098F3A35C0E4788CD00AA54B403336817C763A35C02185860B07A54B40C97ADD925C3A35C0C258CD9B06A54B400EEC1855583A35C057FBA9EE04A54B404ED322C53B3A35C07C6F0CBC05A54B40154B1D131F3A35C083A3D50408A54B40F2AF60660B3A35C04D1A8BF008A54B40BBA74F91003A35C06B7285B80AA54B40A88FEF7AF53935C0688B02800AA54B4040A3A6BDF13935C086AEAE9409A54B40E5014542EC3935C0D3683C6707A54B40B80452B3CF3935C0D4886FB807A54B40F7E995DBB23935C0BBC3A1A709A54B408FAF35499F3935C0F310761B09A54B402A9EAF099E3935C0E485548A01A54B40ABF4FAD4853935C03C23E519FCA44B4046816FB16B3935C097BE7C6DFBA44B40DE2D9781673935C0587C2E87FAA44B40B7389A925F3935C018F4D0C8F6A44B40993D10F14C3935C0C6C4AA75F6A44B4025D4CBF8493935C0D2AED678F5A44B406146BE9D463935C0A3015F21F0A44B404C8D495F293935C0F49221AAEFA44B40946CBAD8253935C005155C29EFA44B40D7CF56C01F3935C0F40BCE02EFA44B402572BD711F3935C039EB3807E7A44B4031A0C728073935C0206D12AAE3A44B40F3F93185003935C0D15CB2E0E2A44B403BBDA179FE3835C094E3D1FEDFA44B40F1D3A661F93835C0EFBC7E87DEA44B409BE3E9C1F53835C0837EA486DBA44B4073BAD189F23835C014F3B40FCFA44B407EDB7C3DDF3835C0461F2E13CDA44B40CBFC65E7DA3835C09F2A9603C3A44B409D51331CCA3835C03DE90ADAB9A44B40D5503F5CB53835C0F1EA0078B2A44B40993C06409E3835C0992DF808ADA44B406BF2B44F853835C0A3A89FF1ACA44B40B2AEAE99843835C0DF105245AAA44B4052DA521B7D3835C0D468A6E5A9A44B406B560F967B3835C04E8904D2A0A44B40164002BA6C3835C004F3E1F09EA44B40A64AF91F693835C0C92E136E94A44B404E23AE17513835C0824A89508CA44B403703C3FD353835C09409EAD786A44B406D8DC9A6183835C09AD5621586A44B40D954CB0A133835C0C7E5727683A44B40DF63224FF53735C08D83868F83A44B4046F5522CD73735C06EBF385084A44B40B27D783CCF3735C074C369327DA44B401B06042EB63735C082AA623378A44B408A791F2F9A3735C0F42B31BF75A44B404845E1057D3735C05BB730E775A44B4011D59F805F3735C0F071F26876A44B4089E03B2B5A3735C010E4110E75A44B408D1EC7A8533735C02D35DF2C71A44B4017D7380B4A3735C099E25F526AA44B402BCE0A41323735C0B8484A7965A44B40DA8CE4C9183735C09719D9DD64A44B40E673EE9E143735C0568C773A64A44B407A8C11FF0F3735C051FA07CE63A44B4033D360BF0C3735C0F34EDA9B63A44B40447AF6330C3735C02422365F5DA44B40115B7B41F33635C0D37E9A4459A44B40A0CFABCCD83635C0EFB4EFD358A44B4038C21460D23635C0D1743D1E56A44B4025CBA1ECC53635C0B18135584DA44B4034D1CFE9B23635C092B2F73E45A44B40FB2B3D1C9A3635C04314502944A44B40A2E4AC14963635C080EF87943EA44B407D13F09A7C3635C047BAAE2A3BA44B40487E4BD1613635C0162CB0843AA44B40A0AAD784523635C03C7C396439A44B40641940C84A3635C0E8E158C437A44B40713C0F152F3635C01FAC237938A44B406BCA2A3F133635C071BC247E3BA44B4091DD10F6F73535C0C5194A4E3CA44B40C1C7A2B8F23535C04025A5073DA44B40AF349B49EF3535C002623D9D3DA44B4086364BCBE63535C03AEAABEF3EA44B40695AB731DB3535C0CD4736753CA44B40427810CDD23535C07F5EBAFC3BA44B40817E5663D03535C0894D660639A44B40D91F7ABDC93535C052CFE66731A44B4066DF76DCB13535C0AF6F97DE2BA44B407078040B983535C0F5BB458D28A44B406DD874EB7C3535C01B49F82E28A44B4043B561DE723535C0CAB1DF8F1EA44B4091ADE2E2613535C09E7B0BF515A44B40D8EC297A4D3535C04BACDD1610A44B406567015D3A3535C0CF138A340EA44B403FDF1FAE383535C02B102A3502A44B40725E295A293535C0D68E21CBF7A34B4082E8481A173535C08CB2920CECA34B40169EEC3B0F3535C0A07F437FDEA34B40C6C8C71D013535C094C5EC5AD2A34B40FA37500AEF3435C04672E2F2C7A34B401F5C947DD93435C023DE8E8EBFA34B402E69790BC13435C0D124E65CBEA34B4095E9E5D8BC3435C08F7BD9DFBAA34B4023C12AAAAD3435C0D91219BBB5A34B40811E27B9A63435C009F51061B3A34B402206BCF6A23435C05B496CCEA8A34B400086C5278F3435C06EABF006A0A34B400FC43979783435C0B5A09B4299A34B407DE8C27B5F3435C0C0CA94AC94A34B40E7A5C3CE443435C0305F318494A34B40BC3B7AE6423435C02E4F48B28FA34B40409863C52D3435C0E4141F0C8CA34B40FA38ED76123435C0AAD6ABB08BA34B405ABE9DC50A3435C03FF6DFAE89A34B40C22D4838F73335C034CC1D9E89A34B4087900868F03335C0FCEE540088A34B409B28822CDA3335C09458396088A34B40256843C2BD3335C0A033E29088A34B40CDB1F05EBA3335C0A6B3B6718BA34B40D2C770CE9D3335C054D2CAC490A34B403A126781823335C092DDAB4B92A34B40150DB0707D3335C0CB6BA91895A34B40B3250EF6703335C014B83BCC9CA34B408ACF0894593335C08521BF149EA34B40613DB6C4563335C0EFCF71E3A4A34B402AD30871423335C004B3963AAFA34B40DDCBE6932C3335C0BFF37699B0A34B4012E3E51E2A3335C00C9BD7C3B0A34B400CF75E98283335C0B06A115FB0A34B40FD7A2C25F63235C0BEDFE8B7ADA34B401189AF68D03235C03EAEE315ADA34B40539E6043AE3235C04DDF6CF3AFA34B40D929AC858C3235C0D1547F93B4A34B4048AAB8256B3235C015355581BAA34B40C3ADCF5B4C3235C0A286C158C3A34B402C136822303235C03714C818D0A34B400A1280E20E3235C0D12FD3A2D7A34B4036A5EF9FFD3135C0E79DE827DBA34B4023AC0687F63135C07107D007DDA34B40624242F5ED3135C0598E92C6EAA34B40B94FFB98AB3135C0753E36AAF4A34B4084075F47653135C0A22343CEF6A34B40BD79CA23583135C0203D182E04A44B40920C9425103135C0ADB6F0CD12A44B407ECF6067C13035C0B2208D9815A44B400EF8681EB43035C0AFE6BA1A22A44B40B95FABF57E3035C0525A574924A44B40D57A2CBF6D3035C0FADE614924A44B40CB8426315D3035C0EC1FC76E25A44B40C785E3A5413035C03511B9D728A44B4092BE54C5263035C0E61A156F2EA44B406C4207360D3035C01E7D341236A44B408EF65D96F52F35C0F73468F73EA44B40DDA2E2CFE12F35C0077ED3273FA44B40F5E98423CE2F35C0FF0BBE703FA44B40E08CFEFCC72F35C004624B7B40A44B40003BDE58BC2F35C0A114F2483AA44B402D5904A4A02F35C035DD3F1336A44B4091BC1C73892F35C0CF166A132CA44B40AFEA0CB3422F35C0A3E4274E29A44B401BE3834E252F35C01710D13229A44B4079597175072F35C061D8E5922AA44B40403B7735E62E35C0AF2300E22AA44B4096E8351BE02E35C0CA3846622BA44B406EFDFDBED72E35C0D66CC7D829A44B40CC83CAEAC62E35C012BF8A5224A44B40BB506AEDA32E35C0D67B74C419A44B40ABCB6A1B752E35C09F04B09314A44B4038B638B9562E35C0EEF5635E12A44B404CED8B16372E35C0B438CE3613A44B40FB07D738172E35C0F294EE3617A44B40BA43E4D8E12D35C08B1768A418A44B40DD5F9F40D32D35C07DC3CBA120A44B400AD6ED3D912D35C05C12406921A44B40D5909B88632D35C0467EC0931EA44B401F97361F272D35C0C0FBEFD313A44B40A08C369DD72C35C0663FE4D4FBA34B40C6080B41262C35C0EF57938BFAA34B40F180D44E1B2C35C0E40DC58BF2A34B40E7BEC94ECB2B35C062A3F334F1A34B40BF58294DB82B35C04BE008D5EFA34B40E22E290D972B35C078098918F0A34B40CCE28241762B35C05C93FB91F3A34B40AA5B5D1C562B35C0CF17E79CF8A34B40323678B3362B35C083E44A6EFEA34B40C498FF6F062B35C0E81CCAD801A44B4074B42157E72A35C0B255D28300A44B407088170BB22A35C07742035EFDA34B406302530F752A35C07D8B18A1FDA34B402A4408594E2A35C011482BA1FFA34B40CF3D0FB92F2A35C021B8CD1703A44B403C07BA37112A35C0220A125909A44B408761DF51F42935C010E5903312A44B40B323B7EBD92935C055C75D611DA44B40DCCFBCD5C22935C0F4035AC12CA44B40AF8CD335A82935C0F93893493BA44B4073851588932935C0710BBE0C47A44B406822E6B5882935C01D2D007C38A44B40423D2F6D772935C0F983FBDB27A44B405F00186D5F2935C0EBDB51DF1BA44B406A06B7D04A2935C0CF214DF111A44B40217BDEA8322935C060A45F9100A44B40636DC0E8FF2835C0499498FEFBA34B407F5AB202F12835C0D005A6FEF3A34B409F53A4C2D32835C04E873057ECA34B4037BEEF72AD2835C04618B77EEAA34B401C95997D9F2835C0881A1DA8DBA34B4042E5641C8A2835C02E341E48CCA34B40D12D4D1C6E2835C06A68E1A4C2A34B40B8E234A1592835C0F0368BCBBAA34B4040A31D9E422835C0A5EFAAEBB4A34B40756A7A9E292835C0E92F0B94B3A34B40F27FBA75222835C0F690D92CB3A34B4032C07982222835C0FF7ACED7A5A34B403D441764222835C0E3C3E6A698A34B40625B43C21E2835C0355BCEE681A34B40361B3562152835C0BE2A890C75A34B40FA67C8430E2835C0AF8170DC68A34B40CD7CCCCF032835C0AD8A5FDC4EA34B404563AE2FE92735C05023450C4CA34B401F1F0030E62735C08691301635A34B40669CAFB9CC2735C01FCF9D721DA34B40D4E6A1C4B52735C0E86E320211A34B40F4C1F54BA72735C07B772702F1A24B40EE16CA4B7B2735C0CFCEA2ECEDA24B40D71CC1DE762735C018E39D8CD8A24B4022B9A2DE562735C0DC9C1F93CAA24B40BFD73A133D2735C037DB29E0B7A24B400318F78C122735C06AEBD37FAEA24B40E67AB250072735C06BD3D837A9A24B404267F032042735C0C6AB385E9CA24B402F171D5FF82635C05D0D79389AA24B4038A95300F62635C0E70866498DA24B400413EBD6E42635C00D47480F82A24B407A35E9DECF2635C02655A4DA78A24B405DECB1AEB72635C0EEFE7EED71A24B40D901C0F39C2635C090E683796DA24B402BDFC86D802635C0CEA6CDE26CA24B405BADA40F772635C007D63FC16CA24B40630E8047762635C0819430C869A24B408D517F4C5F2635C03C6D857668A24B40FA22EDC7472635C0599D3AD268A24B403E288F24302635C07B4E630469A24B40635C8D492C2635C05FA7E6C26BA24B40E6BAF28E0F2635C0CB2406FA70A24B407229980FF42535C0878B02EF72A24B406FFDFC70ED2535C008BBC6C26EA24B407085C78DE52535C052AC26636CA24B40484A9340DF2535C04347D8896BA24B40B47998C4DD2535C0D094590367A24B408AEA3D31D42535C055456A3462A24B409501574EC82535C0F918D9CC5CA24B40F2875571BC2535C0C907451555A24B40548D69E7A42535C0C9A02FC654A24B407BBBDEC5A32535C029A384F84EA24B40E38A058C892535C009553E784BA24B40599EAFEB6D2535C084F2465C4AA24B4065B9B899512535C0D51CE0AB4BA24B407613874F352535C07DC9D3D84CA24B409005528E2C2535C055E7FC634AA24B4055244FEB1E2535C06E241DB549A24B40ACAEA8FC1C2535C06908C4FE42A24B407DDDB7A6062535C07A6E3F113EA24B40843B0BBCEE2435C06975DC063BA24B4042D34DBCD52435C0CC28D5EF39A24B40A466F12CBC2435C0F6F6F9D13AA24B4062176696A22435C03CBD92A83DA24B40451A4281892435C0B73CA2833EA24B40C2FAC2F3832435C0E5A7257941A24B40D760A407752435C070641EBD40A24B409FFF1C155D2435C00AE45F1842A24B406E3E793C422435C0B529429A45A24B402ED8BD15282435C05D420F2E4BA24B408ABA4E3B0F2435C0D9EBD8B252A24B40ABC2E53FF82335C0E1359E5D57A24B40EF5613E8ED2335C071BE8C2057A24B40EB0BD94ADD2335C0DD50BE2B57A24B40DAF8C9B3DC2335C006F5B41E57A24B405C93B867DC2335C018816ED754A24B40A079335FC12335C0D5D2DDCD54A24B405D81B300A62335C02E969A0655A24B408F5BE16FA02335C0140278E557A24B4062711840822335C02E44830457A24B4027ECEA2C802335C00E6C95734FA24B400C925295662335C0831C793A4AA24B4053C3C1014B2335C083E07E7D47A24B4091D2FA312E2335C0FE8C8A6047A24B40DC088AF12B2335C09C47FD3047A24B4069442EDC0E2335C05AD3E7B947A24B40C04FF84E082335C01ED17A2C49A24B40D0F841D3F02235C0B18718784DA24B40BBB999C4D42235C02644FF2754A24B40691D0A69BA2235C0DDA53DB354A24B40AF4C52F2B82235C0B99C57884DA24B40B51A78A8A22235C0BF2C01DC47A24B40CAC2E926882235C087B4458644A24B408A1634486C2235C0E2ED3A9D43A24B40F4FBF2C44F2235C00019E72645A24B404EDC035A332235C06B49056F45A24B40EFDC7E79302235C098EDB9EC45A24B409CCBA9D82C2235C02CA1E43143A24B4081DCCD64272235C0CA03C6B842A24B400C7D7E4B262235C05D341F2C41A24B40C6DB0CFF212235C0FB3C17E43CA24B4019D549E1202235C00E3700B12EA24B40938940C8182235C03361575721A24B4000584B9E0C2235C0CCF12A2B15A24B402AB000B0FC2135C0B39B1E790AA24B402E8FAE61E92135C03B00898401A24B400EDEE22CD32135C062BACB85FAA14B4045066E9DBA2135C06DF1F63BF8A14B408602B53BAE2135C095AFAC5DF5A14B40AA7FE9ADA32135C058559888F0A14B4021C42406862135C008693986EEA14B4048A54D3E672135C0E556B16DEEA14B400C25BE06622135C09C8C59F7EEA14B40216FFD884B2135C0CB56A6E2EEA14B405BDA81074A2135C07F60DD92EFA14B401DA9AECA362135C0D6526472EEA14B402E98D7BF302135C069A72ED0E4A14B40230F5E802E2135C0DB38338FE3A14B400596EF062F2135C01D34BC9BE2A14B4038802B492F2135C0E48A0DADD3A14B40A677D71A312135C0049E0BC7C4A14B40F1B4F47B2E2135C05B6F243DBDA14B40CC8876D82A2135C0084EBE25B8A14B402FB1EDFD282135C0CF7D5321AAA14B40C90C1D681F2135C0459707179DA14B4016AF82CE112135C01546888E9AA14B407B2019140E2135C070895C2895A14B40CE0477E3072135C098A426AC89A14B4066D1166CF52035C01B43DDE67FA14B409A9D19C2DF2035C07B4F00407FA14B4058ED83BADD2035C008BDEEB573A14B4049C7A9BAD52035C078B0FCE452A14B40D485F198B92035C016E4E6E540A14B4017C5895FA52035C05CF0F71420A14B40A436E67E762035C0BE706F0C1AA14B40E198A21C6D2035C062B5267002A14B400E808B2E452035C07812C0A2F9A04B406172BF29342035C06530DA1EF2A04B400FB24D18212035C0E3EB9E8ADFA04B40320FD1B2EA1F35C0E02BEF4AD7A04B403602478ECC1F35C073EDCD3ED2A04B4037738305AC1F35C08F7F0E9CCFA04B4036598EFC911F35C071DA45F5CCA04B40021030D2861F35C019BC1491CBA04B40DBD2C6A6801F35C0E74B39F2BFA04B40B976F5494A1F35C0FFF2C73DBEA04B40BD5CBCA8411F35C037BDA45BABA04B401904AC571F1F35C0C906EDD199A04B40E7659D33011F35C0102F102A90A04B40C3E5BAEFED1E35C04EADF72388A04B40A1789531D81E35C01ACB33ED81A04B405FEDA674C01E35C007502C7B7BA04B4040EE995AA21E35C024784C6D77A04B402F5EEFC98A1E35C091C5B72875A04B407D12FB64721E35C0FCABB7B874A04B4061B92EA5591E35C078A6720675A04B4013B7AF174A1E35C08B5782B872A04B4023BC00A2431E35C0B4AF2D0C6CA04B40037D282F271E35C005CFBA1E68A04B407FD2DEFC081E35C01950F7E367A04B4086E8D92B061E35C0EC56E5C267A04B400CFB85C2021E35C0FBC6F47D65A04B403DB7F06AFE1D35C00373CD5664A04B40D8D3FA59FB1D35C0E6ED848562A04B409A3A0F13F91D35C00359393D5BA04B4079A813BEEC1D35C07FC9E3A059A04B40EC3031FEEA1D35C0ED1599C34EA04B40C2786BCADA1D35C0734C7F4745A04B40E606D5C7C71D35C0E7D0D72144A04B40E5D823ACC41D35C051F6F7393DA04B405A87F53CBD1D35C0E5D48A6931A04B4036DC413EAB1D35C0D544F5082CA04B4089B4FFEB9F1D35C0494638D327A04B40BACE7C9B991D35C02FA148431EA04B400DFC0F42861D35C00DD9915516A04B40E0FA7F76701D35C0E179E73610A04B403D9508B4581D35C070F9476D0FA04B4080A155F3531D35C0737819FD0CA04B40B5B0D9B74B1D35C03B4548030BA04B4098629DDE431D35C08E6699E805A04B4030EDA0DE2A1D35C076E577DD02A04B40BDC5B7B6101D35C01C36FC5702A04B402D6EFD76011D35C0C9DA128BEC9F4B40540300B7F81C35C0CD73C04CDD9F4B40326BC505F01C35C00594B00CCF9F4B408EA85FA7E21C35C076DE4F32C29F4B40EB0ED6FCD01C35C016374C529E9F4B40E82AA15C961C35C0AB64E0649D9F4B40508B7DD3941C35C068982FEB7A9F4B40FAD77BFB5A1C35C0E8A67EF9409F4B40451F3892191C35C0D4D820C9D79E4B408986ECA4B71B35C09691FD5AD29E4B40A133042BB21B35C09610B3FA549E4B408A9A6DAA291B35C0DDAE3B9D4F9E4B40FC7E735D231B35C00C5F19DD019E4B4092190E1DC11A35C0845A5133FA9D4B4029EC2F55B61A35C0B36D3F138F9D4B40BCEE94140F1A35C0A899D6A78A9D4B40A7ADE9BA071A35C02092D9271E9D4B40A177449A471935C0B2B33A5E169D4B405013740C381935C0015A669EBA9C4B409C65DD2B691835C09C339814AF9C4B407B86FF58481835C0D343B8B97E9C4B400716CEAB961735C0C8E7740C4A9C4B40BFEAED10E51635C093313E14459C4B40E883E79DD11635C008A00E67419C4B402756132EBD1635C0C8ED71A6349C4B406225A7EE641635C0113F8270329C4B40AC99BC87621635C0827CC45F279C4B40C641C3EA511635C0AADDC47FED9B4B40EB946BCAEC1535C07B497490E59B4B40EF11F320DD1535C027CDB3B3DE9B4B40897BF3C5CB1535C0DDAAF8F39B9B4B40230681850B1535C0AADCCC8D959B4B403428FDF1F51435C0EC0CB5CF909B4B4013BE49E9DE1435C0EA7BE38F839B4B405F2334498E1435C0EAE8E3A67F9B4B408DCF59E7671435C09E1526477B9B4B4013A559C7FF1335C025CFF6FC7A9B4B40D041916BF01335C0FB1F68BD7B9B4B409CF5A26B3D1335C0892C64827C9B4B40A8BA9B2C281335C03146ECE28B9B4B4015FFD0AC471235C0000789C88C9B4B40EE5DA71B3D1235C05D2D1409A59B4B40C31BF0FB4D1135C006A0A9B4A59B4B4042989FF2471135C0D820AEC1BF9B4B405D795526701035C08372A486D59B4B40A944E8899D0F35C080054E94D69B4B400FC41A835D0F35C0E1EB5A49D59B4B40847AB74F090F35C0E72FEA00D69B4B40F112D1C8ED0E35C0EDD112FBD89B4B401F011DCCD20E35C0DDCB2B3BE09B4B40FEEB2FCCA30E35C0C4B3A371E49B4B404FE0C4F38D0E35C04D9BC623EA9B4B400C4A8E53790E35C02A189E38F19B4B40DA27EF45660E35C01410A478069C4B4061D91466340E35C06B7C230D0C9C4B400B56C890290E35C005126F0C0C9C4B4008DD3BB5280E35C0337609DF0C9C4B4008CCD7EFF60D35C0963FDE560E9C4B406BD4CECDDC0D35C0EFFC75D8119C4B40DE29285DC30D35C0633A127C169C4B4075F5D4D6AE0D35C055990DD50C9C4B409BA08F1CA40D35C048CE1F78019C4B404248957C920D35C0F6932178E89B4B40B6F36E1C650D35C04B587F1CDE9B4B40E7A0B3E54E0D35C0627A60DBD59B4B400AB5B8C4350D35C0D6A33FCBD19B4B40A7ABB001230D35C004D5165ECD9B4B4079DB72661D0D35C058098E2FC39B4B40116E48F80B0D35C0AB010576BA9B4B400C006AF2F70C35C0F0470E76AF9B4B40E9535792DA0C35C052275044A89B4B40AB859DF6C30C35C02FE9C5EEA29B4B405A181D9BAB0C35C0A3180C41999B4B4025A19D00740C35C0EBE4D2516A9B4B401E31219CC00B35C07E0098FA649B4B403A303F8AA70B35C0BAFC6EB9619B4B4032545A3D8D0B35C09A3F9AA1609B4B40610C1F51720B35C0F5E9A5A1609B4B40BAC3DDF45F0B35C072408AFC589B4B40AF0D0E164D0B35C0CF8BA609529B4B40595179F4340B35C0926FC4264D9B4B40DDFC2A1A1B0B35C05A6ABD714A9B4B405E6D1725000B35C03C7D1AFB499B4B40754FF2B9E40A35C0D015879F4A9B4B40D72CD54BCA0A35C0', 'La Réunion'); - -CREATE INDEX sidx_300_metres_geom ON prod."300_metres" USING gist (geom); diff --git a/datascience/tests/test_data/cacem_database/V666.504__regulatory_areas_themes_and_tags.sql b/datascience/tests/test_data/cacem_database/V666.504__regulatory_areas_themes_and_tags.sql deleted file mode 100644 index d2d2e59245..0000000000 --- a/datascience/tests/test_data/cacem_database/V666.504__regulatory_areas_themes_and_tags.sql +++ /dev/null @@ -1,69 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS prod; -DROP TABLE IF EXISTS prod."REG_ENV_V3" CASCADE; - -CREATE TABLE IF NOT EXISTS prod."REG_ENV_V3" -( - id serial primary key, - geom geometry(MultiPolygon, 4326), - ent_name varchar, - url varchar, - layer_name varchar, - facade varchar, - ref_reg varchar, - edition date, - editeur varchar, - source varchar, - obs varchar, - thematique varchar, - date date, - validite varchar, - date_fin date, - tempo varchar, - type varchar, - resume text, - poly_name text, - plan text -); - -/* THEMES */ -DROP TABLE IF EXISTS prod.themes CASCADE; -CREATE TABLE prod.themes ( - id SERIAL PRIMARY KEY, - "name" varchar, - parent_id INT REFERENCES prod.themes (id), - started_at timestamp, - ended_at timestamp, - control_plan_themes_id INT, - control_plan_sub_themes_id INT, - control_plan_tags_id INT, - reportings_control_plan_sub_themes_id INT -); - -/* REGULATORY_AREAS_THEMES */ -DROP TABLE IF EXISTS prod.themes_regulatory_areas; -CREATE TABLE prod.themes_regulatory_areas ( - themes_id INT REFERENCES prod.themes (id), - regulatory_areas_id INT REFERENCES prod."REG_ENV_V3" (id), - PRIMARY KEY (themes_id, regulatory_areas_id) -); - -/* TAGS */ -DROP TABLE IF EXISTS prod.tags CASCADE; -CREATE TABLE prod.tags ( - id SERIAL PRIMARY KEY, - "name" varchar, - parent_id INT REFERENCES prod.tags (id), - started_at timestamp, - ended_at timestamp -); - -/* REGULATORY_AREAS_TAGS */ -DROP TABLE IF EXISTS prod.tags_regulatory_areas; -CREATE TABLE prod.tags_regulatory_areas ( - tags_id INT REFERENCES prod.tags (id), - regulatory_areas_id INT REFERENCES prod."REG_ENV_V3" (id), - PRIMARY KEY (tags_id, regulatory_areas_id) -); - -insert into prod."REG_ENV_V3" (id, geom, ent_name, url, layer_name, facade, ref_reg, edition, editeur, source, obs, thematique, date, validite, date_fin, tempo, type, resume, poly_name, plan) values (1, 'MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)))', 'entity_name1', 'url1', 'layer_name1', 'MED', 'ref_reg1', '2025-01-01', 'editrice1', 'source1', 'observation1', 'thematique1', '2010-06-01', '10 ans', '2024-01-01', 'temporaire', 'Décret', 'resume1', 'polyname1', 'plan1'); -insert into prod."REG_ENV_V3" (id, geom, ent_name, url, layer_name, facade, ref_reg, edition, editeur, source, obs, thematique, date, validite, date_fin, tempo, type, resume, poly_name, plan) values (2, 'MULTIPOLYGON(((120 -20,135 -20,135 -10,120 -10,120 -20)))', 'entity_name2', 'url2', 'layer_name2', 'NAMO', 'ref_reg2', '2025-01-01', 'editeur2', 'source2', 'observation2', 'thematique2', '2005-07-01', '20 ans', '2025-01-01', 'permanent', 'Arrêté préfectoral', 'resume2', 'polyname2', 'plan2'); \ No newline at end of file diff --git a/datascience/tests/test_data/cacem_database/V666.505__localized_areas.sql b/datascience/tests/test_data/cacem_database/V666.505__localized_areas.sql deleted file mode 100644 index 924d5181ea..0000000000 --- a/datascience/tests/test_data/cacem_database/V666.505__localized_areas.sql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS prod; -DROP TABLE IF EXISTS prod.localized_areas; -CREATE TABLE prod.localized_areas ( - id SERIAL PRIMARY KEY, - geom geometry(MultiPolygon,4326), - "name" character varying, - group_name character varying, - control_unit_ids INTEGER[], - amp_ids INTEGER[] -); - - -INSERT INTO prod.localized_areas VALUES (1, '0106000020E61000000100000001030000000100000008000000A26148C2679001C056BFFE3E3D7D47407FEC0E55159501C0E2F2B845B37D4740FC031AFB7A9D01C084717475407E474074D93921277F01C0740323C9F37E47403905F682AC7A01C0FD67E71AD27E4740805C4235A96001C007C2FBF08C7D4740FB6146C2679001C033E6FD3E3D7D4740A26148C2679001C056BFFE3E3D7D4740', 'Châtelet', 'Cultures marines 85', '{10000,10018}', '{3}'); - - -CREATE INDEX sidx_localized_areas_geom ON prod.localized_areas USING gist (geom); - diff --git a/datascience/tests/test_data/cacem_database/V666.506__beaches.sql b/datascience/tests/test_data/cacem_database/V666.506__beaches.sql deleted file mode 100644 index e8a1bcbb09..0000000000 --- a/datascience/tests/test_data/cacem_database/V666.506__beaches.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE SCHEMA IF NOT EXISTS prod; -DROP TABLE IF EXISTS prod.plageslitto; -CREATE TABLE prod.plageslitto ( - id SERIAL PRIMARY KEY, - geom geometry(MultiPoint,4326), - nom character varying, - nom_offici character varying, - code_posta character varying, - insee character varying -); - - -INSERT INTO prod.plageslitto VALUES (1527, '0104000020E61000000100000001010000007524453440EA1A408C6C58423AB24540', 'La Galiote', 'Fréjus', '83370', '83061'); - - -CREATE INDEX sidx_beaches_geom ON prod.plageslitto USING gist (geom); diff --git a/datascience/tests/test_data/emails/expected_rendered_email.html b/datascience/tests/test_data/emails/expected_rendered_email.html deleted file mode 100644 index d670a0d7e1..0000000000 --- a/datascience/tests/test_data/emails/expected_rendered_email.html +++ /dev/null @@ -1,154 +0,0 @@ - - Bilan hebdomadaire contrôle de l'environnement marin - - - - -
-

Bilan hebdomadaire contrôle de l'environnement marin

-
-
-
-
-

Bonjour,

-

Vous trouverez ci-dessous les données des contrôles et des surveillances de l'environnement marin effectués par votre unité - (Nom de l'unité) entre le 23/06/2020 00:00 UTC et le 06/05/2020 18:45 UTC que vous avez rapportés au Centre d'Appui au Contrôle de l'Environnement Marin (CACEM), - ainsi que les éventuels contrôles et surveillances de l'environnement marin réalisés antérieurement et ayant été rapportés ou mis à jour pendant cette période.

-

Seuls les contrôles et surveillances dont les données sont complètes sont transmis dans ce bilan hebdomadaire. Si certaines données n'ont pas encore été transmises (par ex. l'établissement d'un PV ou non), - il est normal que le contrôle ne figure pas encore dans le rapport.

-

Si des données sont manquantes, incorrectes ou incomplètes, ou pour toute remarque concernant ce bilan, n'hésitez pas à contacter le CACEM : cacem@email.fr.

-
-
-
-

Contrôles et surveillances réalisés la semaine passée

-

Contrôles

- - - - - - - - - - - - - - - - - - - - - - - -
Date du contrôleType de missionFaçadeDépartementInfraction relevéeNombre de contrôlesThématique(s) de contrôle
2022-11-24 20:31MerHors façadeHors départementNon0Aucun thème (Aucun sous-thème)
- -

Surveillances

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Début de surveillanceFin de surveillanceType de missionFaçadeDépartementDurée (h)Thématique(s) de surveillance
2022-11-28 13:592022-12-05 19:59MerHors façadeHors département174.0Culture marine (Implantation), Police des espèces protégées et de leurs habitats (faune et flore) (Dérogations concernant les espèces protégées, Détention d'espèces protégées)
2022-11-20 20:312022-11-20 23:31MerHors façadeHors département3.0Activités et manifestations soumises à évaluation d’incidence Natura 2000 (Aucun sous-thème)
-
-
-
-

Contrôles et surveillances antérieurs rapportés ou mis à jour la semaine passée

-

Contrôles

- - - - - - - - - - - - - - - - - - - - - - - -
Date du contrôleType de missionFaçadeDépartementInfraction relevéeNombre de contrôlesThématique(s) de contrôle
2022-11-24 20:31MerHors façadeHors départementNon0Aucun thème (Aucun sous-thème)
- -

Surveillances

- - - - - - - - - - - - - - - - - - - - - - - -
Début de surveillanceFin de surveillanceType de missionFaçadeDépartementDurée (h)Thématique(s) de surveillance
2022-11-28 13:592022-12-05 19:59MerHors façadeHors département54.0Culture marine (Implantation), Police des espèces protégées et de leurs habitats (faune et flore) (Dérogations concernant les espèces protégées, Détention d'espèces protégées)
-
-
-
-

- Pour réaliser vos bilans, ces données sont stockées, mises en forme en tableaux de bord et accessibles en temps réel sur - l'interface Metabase : pour obtenir vos accès nominatifs, veuillez vous rapprocher de - Mike (mike.data@ana.lyst), analyste de données pour le CACEM et administrateur Metabase. -

-
-
-
-
-

Centre d'Appui au Contrôle de l'Environnement Marin - Tél : +33 2 90 74 32 55

-
- - \ No newline at end of file diff --git a/datascience/tests/test_data/emails/expected_rendered_email_without_actions.html b/datascience/tests/test_data/emails/expected_rendered_email_without_actions.html deleted file mode 100644 index 461a2a94a0..0000000000 --- a/datascience/tests/test_data/emails/expected_rendered_email_without_actions.html +++ /dev/null @@ -1,53 +0,0 @@ - - Bilan hebdomadaire contrôle de l'environnement marin - - - - -
-

Bilan hebdomadaire contrôle de l'environnement marin

-
-
-
-
-

Bonjour,

-

Vous trouverez ci-dessous les données des contrôles et des surveillances de l'environnement marin effectués par votre unité - (Nom de l'unité) entre le 23/06/2020 00:00 UTC et le 06/05/2020 18:45 UTC que vous avez rapportés au Centre d'Appui au Contrôle de l'Environnement Marin (CACEM), - ainsi que les éventuels contrôles et surveillances de l'environnement marin réalisés antérieurement et ayant été rapportés ou mis à jour pendant cette période.

-

Seuls les contrôles et surveillances dont les données sont complètes sont transmis dans ce bilan hebdomadaire. Si certaines données n'ont pas encore été transmises (par ex. l'établissement d'un PV ou non), - il est normal que le contrôle ne figure pas encore dans le rapport.

-

Si des données sont manquantes, incorrectes ou incomplètes, ou pour toute remarque concernant ce bilan, n'hésitez pas à contacter le CACEM : cacem@email.fr.

-
-
-
-

Contrôles et surveillances réalisés la semaine passée

-

Contrôles

- Aucun - -

Surveillances

- Aucune -
-
-
-

Contrôles et surveillances antérieurs rapportés ou mis à jour la semaine passée

-

Contrôles

- Aucun - -

Surveillances

- Aucune -
-
-
-

- Pour réaliser vos bilans, ces données sont stockées, mises en forme en tableaux de bord et accessibles en temps réel sur - l'interface Metabase : pour obtenir vos accès nominatifs, veuillez vous rapprocher de - Mike (mike.data@ana.lyst), analyste de données pour le CACEM et administrateur Metabase. -

-
-
-
-
-

Centre d'Appui au Contrôle de l'Environnement Marin - Tél : +33 2 90 74 32 55

-
- - \ No newline at end of file diff --git a/datascience/tests/test_data/remote_database/V666.0__Reset_themes_and_tags.sql b/datascience/tests/test_data/remote_database/V666.0__Reset_themes_and_tags.sql deleted file mode 100644 index ad2f60b2d1..0000000000 --- a/datascience/tests/test_data/remote_database/V666.0__Reset_themes_and_tags.sql +++ /dev/null @@ -1,30 +0,0 @@ -DELETE FROM themes_env_actions; -DELETE FROM themes_regulatory_areas; -DELETE FROM themes; -DELETE FROM tags_regulatory_areas; -DELETE FROM tags; - -INSERT INTO themes (id, name, started_at, ended_at, control_plan_themes_id) -SELECT DISTINCT id, theme, '2023-01-01 00:00:00'::timestamp, '2099-12-31 23:59:59'::timestamp, id -FROM control_plan_themes; - -select setval('themes_id_seq', (SELECT MAX(id) FROM themes)); - -INSERT INTO themes (name, parent_id, started_at, ended_at, control_plan_sub_themes_id) -SELECT DISTINCT subtheme, theme_id, '2023-01-01 00:00:00'::timestamp, '2023-12-31 23:59:59'::timestamp, id -FROM control_plan_sub_themes -where year = 2023 ORDER BY ID; - -INSERT INTO themes (name, parent_id, started_at, ended_at, control_plan_sub_themes_id) -SELECT DISTINCT subtheme, theme_id, '2024-01-01 00:00:00'::timestamp, '2024-12-31 23:59:59'::timestamp, id -FROM control_plan_sub_themes -where year = 2024 ORDER BY ID; - -INSERT INTO themes (name, parent_id, started_at, ended_at, control_plan_sub_themes_id) -SELECT DISTINCT subtheme, theme_id, '2025-01-01 00:00:00'::timestamp, '2025-12-31 23:59:59'::timestamp, id -FROM control_plan_sub_themes -where year = 2025 ORDER BY ID; - -INSERT INTO themes (name, parent_id, started_at, ended_at, control_plan_tags_id) -SELECT DISTINCT tag, theme_id, '2023-01-01 00:00:00'::timestamp, '2099-12-31 23:59:59'::timestamp, id -FROM control_plan_tags ORDER BY ID; \ No newline at end of file diff --git a/datascience/tests/test_data/remote_database/V666.17__Reset_test_eez_areas.sql b/datascience/tests/test_data/remote_database/V666.17__Reset_test_eez_areas.sql deleted file mode 100644 index 05bc1bef3e..0000000000 --- a/datascience/tests/test_data/remote_database/V666.17__Reset_test_eez_areas.sql +++ /dev/null @@ -1,8 +0,0 @@ -DELETE FROM eez_areas; - -INSERT INTO eez_areas ( - territory1, iso_sov1, wkb_geometry -) VALUES -('France', 'FRA', ST_Collect(ARRAY [ST_Polygon('LINESTRING(10.0 45.0, -10.0 45.0, -10.0 0.0, 10.0 0.0, 10.0 45.0)'::geometry, 4326)])), -('Autre Zone de la ZEE française', 'FRA', ST_Collect(ARRAY [ST_Polygon('LINESTRING(10.0 50.0, -10.0 50.0, -10.0 60.0, 10.0 60.0, 10.0 50.0)'::geometry, 4326)])), -('Estonia', 'EST', ST_Collect(ARRAY [ST_Polygon('LINESTRING(10.0 45.0, -10.0 45.0, -10.0 50.0, 10.0 50.0, 10.0 45.0)'::geometry, 4326)])); diff --git a/datascience/tests/test_data/remote_database/V666.1__Reset_test_controllers.sql b/datascience/tests/test_data/remote_database/V666.1__Reset_test_controllers.sql deleted file mode 100644 index 5a9b9b05ee..0000000000 --- a/datascience/tests/test_data/remote_database/V666.1__Reset_test_controllers.sql +++ /dev/null @@ -1,75 +0,0 @@ -DELETE FROM missions_control_resources; -DELETE FROM control_unit_resources; -DELETE FROM missions_control_units; -DELETE FROM control_units; -DELETE FROM bases; - -ALTER SEQUENCE public.control_units_id_seq RESTART WITH 10000; - -INSERT INTO public.control_units ( - administration_id, name) VALUES - ( 1005, 'Cultures marines – DDTM 30'), - ( 1005, 'Cultures marines – DDTM 40'), - ( 1005, 'DML – DDTM 59'), - ( 1005, 'DML 2A'), - ( 1005, 'DPM – DDTM 14'), - ( 1005, 'DPM – DDTM 35'), - ( 1005, 'DPM – DDTM 44'), - ( 1005, 'SML 33'), - ( 1005, 'SML 50'), - ( 1005, 'Police de l''eau – DDTM 11'), - ( 1009, 'Cross Etel'), - ( 1009, 'Cross Gris Nez'), - ( 2, 'BGC Ajaccio'), - ( 2, 'BGC Bastia'), - ( 2, 'BSN Ste Maxime'), - ( 2, 'DF 25 Libecciu'), - ( 2, 'DF 61 Port-de-Bouc'), - ( 1008, 'DREAL Pays-de-La-Loire'), - ( 4, 'P602 Verdon'), - ( 6, 'BN Toulon'), - ( 6, 'Brigade fluviale de Rouen'), - ( 1013, 'Natura 2000 Côte Bleue Marine'), - ( 3, 'A636 Maïto'), - ( 1011, 'OFB Sd 85'), - ( 1011, 'OFB SD974 Brigade Nature – SOI'), - ( 1014, 'Parc Naturel Régional Martinique'), - ( 10, 'Parc National de Guadeloupe'), - ( 1006, 'PNM Martinique'), - ( 1015, 'Police Municipale Le Marin 972'), - ( 1004, 'Réserve Naturelle de L''Ilot M''Bouzi'), - ( 1004, 'Réserve Naturelle 7 Iles'); - -INSERT INTO public.control_units( - id, administration_id, name) VALUES - ( 10121, 1009, 'PAM Jeanne Barret'), - ( 10080, 1009, 'PAM Themis'); - -INSERT INTO public.bases( - id, latitude, longitude, name) VALUES - ( 1, 45.569, -2.569, 'L''Etoile Noire'); - -INSERT INTO public.control_unit_resources( - id, base_id, control_unit_id, name, type) VALUES - ( 8, 1, 10019, 'PAM Jeanne Barret', 'PATROL_BOAT'), - ( 10, 1, 10018, 'PAM Themis', 'PATROL_BOAT'); - -INSERT INTO public.control_unit_contacts ( - control_unit_id, email, name, phone, created_at_utc, updated_at_utc, is_email_subscription_contact, is_sms_subscription_contact) VALUES - ( 10018, 'p602@ca.plane.pour.moi', 'UNIT_CHIEF', 'Balance ton 06', '2020-01-05 12:36:52Z', '2022-01-05 10:36:22Z', false, true), - ( 10018, 'diffusion.p602@email.fr', 'OFFICE', NULL, '2020-02-08 00:36:42Z', '2022-01-05 10:37:22Z', true, true), - ( 10018, 'diffusion_bis.p602@email.fr', 'OFFICE', NULL, '2020-02-08 00:36:42Z', '2022-01-05 10:37:22Z', true, true), - ( 10019, 'bn_toulon@email.fr', 'OFFICE', 'Le 07 du chef', '2020-03-21 10:35:20Z', '2022-01-05 10:38:22Z', true, true), - ( 10002, 'dml59@surveillance.fr', 'Jean-Mich du 59', '0000000000', '2020-01-05 09:35:39Z', '2022-01-05 10:39:22Z', true, true); - --- --- Add historic control units --- -INSERT INTO public.control_units ( - id, administration_id, name, archived) VALUES - (1315, 1011, 'Unité 1 ancien nom', true), - (1485, 4, 'Unité 3 ancien nom', true); - -INSERT INTO public.control_unit_contacts ( - control_unit_id, email, name, phone, created_at_utc, updated_at_utc, is_email_subscription_contact, is_sms_subscription_contact) VALUES - ( 1315, 'email.perime@unit.archivee', 'UNIT_CHIEF', '9876543210', '2010-06-02 11:40:28Z', '2020-01-01 00:00:00Z', true, true); diff --git a/datascience/tests/test_data/remote_database/V666.26__Reset_test_infractions.sql b/datascience/tests/test_data/remote_database/V666.26__Reset_test_infractions.sql deleted file mode 100644 index 5e57eddb57..0000000000 --- a/datascience/tests/test_data/remote_database/V666.26__Reset_test_infractions.sql +++ /dev/null @@ -1,13 +0,0 @@ -DELETE FROM natinfs; - -INSERT INTO natinfs ( - natinf_code, regulation, infraction_category, infraction) VALUES - ( 22206, 'Reg pêche 1', 'Pêche', 'Infraction 1'), - ( 27724, 'Reg pêche 5', 'Pêche', 'Infraction 2'), - ( 22222, 'Reg pêche 2', 'Pêche', 'Infraction 3'), - ( 17, 'Reg pêche 3', 'Pêche', 'Infraction 4'), - ( 1030, 'Reg pêche 1', 'Pêche', 'Infraction 5'), - ( 1000, 'Reg pêche 1', 'Sécurité / Rôle', 'Infraction 6'), - ( 2000, 'Reg pêche 2', 'Sécurité / Rôle', 'Infraction 7'), - ( 3000, 'Reg pêche 2', 'Environnement', 'Infraction 8'), - ( 4000, 'Reg pêche 9', 'Environnement', 'Infraction 9'); diff --git a/datascience/tests/test_data/remote_database/V666.27__Reset_test_amps.sql b/datascience/tests/test_data/remote_database/V666.27__Reset_test_amps.sql deleted file mode 100644 index 876a79c01f..0000000000 --- a/datascience/tests/test_data/remote_database/V666.27__Reset_test_amps.sql +++ /dev/null @@ -1 +0,0 @@ -DELETE FROM amp_cacem; \ No newline at end of file diff --git a/datascience/tests/test_data/remote_database/V666.28__Reset_test_regulatory_areas.sql b/datascience/tests/test_data/remote_database/V666.28__Reset_test_regulatory_areas.sql deleted file mode 100644 index cdbe137a91..0000000000 --- a/datascience/tests/test_data/remote_database/V666.28__Reset_test_regulatory_areas.sql +++ /dev/null @@ -1,9 +0,0 @@ --- Supprimer les données des tables dépendantes -DELETE FROM themes_regulatory_areas; -DELETE FROM tags_regulatory_areas; -DELETE FROM dashboard_datas; - -DELETE FROM regulations_cacem; - -insert into public.regulations_cacem (id, geom, entity_name, url, layer_name, facade, ref_reg, edition, editeur, source, observation, thematique, date, duree_validite, date_fin, temporalite, type, row_hash) values (1, 'MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)))', 'entity_name1', 'url1', 'layer_name1', 'MED', 'ref_reg1', '2025-01-01', 'editrice1', 'source1', 'observation1', 'thematique1', '2010-06-01', '10 ans', '2024-01-01', 'temporaire', 'Décret', '4ccc708e6a0c4f311dd7e537e282f7f6'); -insert into public.regulations_cacem (id, geom, entity_name, url, layer_name, facade, ref_reg, edition, editeur, source, observation, thematique, date, duree_validite, date_fin, temporalite, type, row_hash) values (2, 'MULTIPOLYGON(((120 -20,135 -20,135 -10,120 -10,120 -20)))', 'entity_name2', 'url2', 'layer_name2', 'NAMO', 'ref_reg2', '2025-01-01', 'editeur2', 'source2', 'observation2', 'thematique2', '2005-07-01', '20 ans', '2025-01-01', 'permanent', 'Arrêté préfectoral', '8c3842144dfaf46ead39bfa628dd9513'); \ No newline at end of file diff --git a/datascience/tests/test_data/remote_database/V666.2__Reset_test_missions.sql b/datascience/tests/test_data/remote_database/V666.2__Reset_test_missions.sql deleted file mode 100644 index 41a584137e..0000000000 --- a/datascience/tests/test_data/remote_database/V666.2__Reset_test_missions.sql +++ /dev/null @@ -1,47 +0,0 @@ - -DELETE FROM themes_env_actions; -DELETE FROM env_actions; -DELETE FROM missions; - -INSERT INTO public.missions ( - id, mission_types, open_by, observations_cacem, facade, start_datetime_utc, end_datetime_utc, created_at_utc, updated_at_utc, completed_by, deleted, mission_source, geom) VALUES - (12, '{SEA}', 'Kimberly Woodward', 'Mother including baby same. Evidence project air practice minute their. Trouble sing suggest maintain like know too.', 'NAMO', '2022-02-24 10:56:33', '2022-05-06 19:38:29', '2022-02-24 10:56:33', '2022-05-06 19:38:29', 'Charles Kennedy', false, 'MONITORENV', ST_GeomFromText('MULTIPOLYGON(((-4 44,-4.1 44,-4.1 48,-4 48,-4 44)))', 4326)), - (13, '{LAND}', 'Tyler Dickerson', 'Receive hit themselves. Example community suggest seek to technology.', 'NAMO', '2022-02-07 04:16:43', '2022-07-10 19:55:50', '2022-02-07 04:16:43', '2022-07-10 19:55:50', 'Robin Keller', false, 'MONITORENV', ST_GeomFromText('MULTIPOLYGON(((-4 41,-4.1 41,-4.1 46,-4 46,-4 41)))', 4326)), - (19, '{SEA}', 'Scott Lopez', 'Difficult ahead let really old around. Cover operation seven surface use show. Manage beautiful reason account prepare evening sure.', 'NAMO', '2022-06-21 13:24:04', '2022-07-18 02:49:08', '2022-06-21 13:24:04', '2022-11-18 02:49:08', 'Edward Gutierrez', false, 'MONITORENV', ST_GeomFromText('MULTIPOLYGON(((25 40,26 40,26 41,25 41,25 40)))', 4326)), - (20, '{LAND}', 'Casey Houston', 'South add memory sing population. Entire particularly deep yard avoid. ', 'MED', '2022-06-18 08:08:01', '2022-08-09 02:29:02', '2022-06-18 08:08:01', '2022-08-09 02:29:02', 'Sara Cook', false, 'MONITORENV', NULL); - - --- Control units keys starts at 10000 -INSERT INTO missions_control_units ( - mission_id, control_unit_id) VALUES - ( 12, 10019), - ( 12, 10018), - ( 13, 10019), - ( 19, 10019), - ( 20, 10003); - -INSERT INTO missions_control_resources ( - mission_id, control_resource_id) VALUES - ( 12, 10), - ( 13, 8); - --- --- Name: missions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('public.missions_id_seq', 21, true); - --- --- Add historic data imported from Poseidon with a shift of -100000 on ids --- - -INSERT INTO public.missions ( - id, mission_types, open_by, start_datetime_utc, end_datetime_utc, completed_by, deleted, mission_source -) VALUES - (-95689, '{SEA}', 'Mike', NOW() - INTERVAL '5 days', NOW() - INTERVAL '4 days', 'Mike', false, 'POSEIDON_CACEM'), - (-95690, '{LAND}', 'John', NOW() - INTERVAL '1 day', NOW() - INTERVAL '6 hours', 'John', false, 'POSEIDON_CACEM'); - -INSERT INTO missions_control_units ( - mission_id, control_unit_id) VALUES - ( -95689, 1315), - ( -95690, 1315); diff --git a/datascience/tests/test_data/remote_database/V666.30__Reset_test_localized_areas.sql b/datascience/tests/test_data/remote_database/V666.30__Reset_test_localized_areas.sql deleted file mode 100644 index 6c65b8923e..0000000000 --- a/datascience/tests/test_data/remote_database/V666.30__Reset_test_localized_areas.sql +++ /dev/null @@ -1 +0,0 @@ -DELETE FROM localized_areas; \ No newline at end of file diff --git a/datascience/tests/test_data/remote_database/V666.3__insert_dummy_env_actions.sql b/datascience/tests/test_data/remote_database/V666.3__insert_dummy_env_actions.sql deleted file mode 100644 index 7d4c18b569..0000000000 --- a/datascience/tests/test_data/remote_database/V666.3__insert_dummy_env_actions.sql +++ /dev/null @@ -1,23 +0,0 @@ - --- --- Data for Name: env_actions; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO public.env_actions ( - id, mission_id, action_type, completion, value, action_start_datetime_utc, action_end_datetime_utc, geom) VALUES - ('dfb9710a-2217-4f98-94dc-283d3b7bbaae', 12, 'SURVEILLANCE', 'COMPLETED', '{}', '2022-11-20 20:31:41.719', '2022-11-20 23:31:41.719', ST_GeomFromText('MULTIPOLYGON(((-1.22 46.27,-1.22 46.27,-1.20 46.26,-1.19 46.26,-1.22 46.27)))', 4326)), - ('d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2', 12, 'CONTROL', 'COMPLETED', '{"infractions": [], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "protectedSpecies": [], "actionNumberOfControls": 0, "actionStartDateTimeUtc": null}', '2022-11-24 20:31:41.719', NULL, ST_GeomFromText('MULTIPOINT(-2.9822 48.1236,-3.0564 48.1177)', 4326)), - ('88713755-3966-4ca4-ae18-10cab6249485', 19, 'SURVEILLANCE', 'COMPLETED', '{"observations": "Surveillance ok", "protectedSpecies": []}', '2022-06-28 13:59:20.176', '2022-07-05 19:59:20.176', ST_GeomFromText('MULTIPOLYGON(((25 40,26 40,26 41,25 41,25 40)))', 4326)), - ('b05d96b8-387f-4599-bff0-cd7dab71dfb8', 20, 'CONTROL', 'TO_COMPLETE', '{"infractions": [{"id": "c52c6f20-e495-4b29-b3df-d7edfb67fdd7", "natinf": ["10038", "10054"], "toProcess": false, "vesselSize": "FROM_24_TO_46m", "vesselType": "COMMERCIAL", "companyName": null, "formalNotice": "PENDING", "observations": "Pas d''observations", "infractionType": "WITH_REPORT", "registrationNumber": "BALTIK", "controlledPersonIdentity": "John Doe"}], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "protectedSpecies": [], "actionNumberOfControls": 1, "actionStartDateTimeUtc": null}', '2022-11-17 13:59:51.108', NULL, ST_GeomFromText('MULTIPOINT(-2.52 47.16)', 4326)), - ('dedbd2c2-10f5-4d75-8fe9-c50db2ae5d0b', 20, 'CONTROL', 'TO_COMPLETE', '{"infractions": [], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "protectedSpecies": [], "actionNumberOfControls": 0, "actionStartDateTimeUtc": null}', '2022-11-24 20:31:41.719', NULL, NULL); - - INSERT INTO themes_env_actions ( - env_actions_id, themes_id) VALUES - ('dfb9710a-2217-4f98-94dc-283d3b7bbaae', 1), - ('b05d96b8-387f-4599-bff0-cd7dab71dfb8', 1), - ('b05d96b8-387f-4599-bff0-cd7dab71dfb8', 146), - ('88713755-3966-4ca4-ae18-10cab6249485', 107), - ('88713755-3966-4ca4-ae18-10cab6249485', 14), - ('88713755-3966-4ca4-ae18-10cab6249485', 238), - ('88713755-3966-4ca4-ae18-10cab6249485', 167), - ('88713755-3966-4ca4-ae18-10cab6249485', 169); diff --git a/datascience/tests/test_data/remote_database/V666.4__Reset_dummy_departments_areas.sql b/datascience/tests/test_data/remote_database/V666.4__Reset_dummy_departments_areas.sql deleted file mode 100644 index 85bec976e4..0000000000 --- a/datascience/tests/test_data/remote_database/V666.4__Reset_dummy_departments_areas.sql +++ /dev/null @@ -1,11 +0,0 @@ -DELETE FROM public.departments_areas; - -INSERT INTO public.departments_areas ( - insee_dep, name, geometry) VALUES -( '14', 'Calvados', '0106000020E610000001000000010300000001000000080000005D357ED76AEAF1BF7D554E6DCDAD48402A9E9376DC91EDBF68770E2D80D74840359BAA0792E9D23F4F19CC0121B848404AC70D3E3C93DC3F88529B26EB8148408412874AD441C0BF971F86A98468484044F2C1EBFB4CF1BF66E5659B82634840C3A62BBD838DEBBF35D22832F98348405D357ED76AEAF1BF7D554E6DCDAD4840'), -( '29', 'Finistère', '0106000020E6100000010000000103000000010000000A0000005C07E2D47E1C0DC0949A59746F564840E208359EC2270CC06737CD7040184840126C0E13D8DD0DC0B615E0C1560C4840A341D6B0C8170BC07CB7D42B93F6474076842435CE2C0DC03F619A1AF2B4474049D5471E5ADD14C0E1573AAC36F3474059831AF61DCD15C0C3D0199463364840D7D55439811315C0D69D570466544840FAAFA3FB33DF0EC09CBEDF571C8448405C07E2D47E1C0DC0949A59746F564840'), -( '44', 'Loire-Atlantique', '0106000020E6100000010000000103000000010000000C0000009513670CCFFAEEBF60C3305931B047409CEA73D2CCAAF5BF4217B282EEA647402F94393AECDDF1BF0884FAC34D854740E8E04BEC8A55F7BF62CB69ED867647408C24BD9B080FF8BFD360FCAA068547403F112A04B2C6F8BFF4E9EE09176E47405FC02103758AFFBF0A8001CDA9834740DB8D06C9999B08C0E16FD424F7894740A5DDC755555505C0893CEE00F4B44740F448B52CBFA7F7BF0B664852FCEA474014A6DE28AD2AF0BF97E651CEF1CB47409513670CCFFAEEBF60C3305931B04740'), -( '50', 'Manche', '0106000020E6100000010000000103000000010000000D000000B8C51A3A923EF2BFBEF8954D85A748409393F468D06DECBFA4FCD309089A4840C3A62BBD838DEBBF35D22832F9834840D75A411FC18DF2BF8ABA1324C56948402ADAC171DEA0E7BF7DE08315AB4F48406FA7431DEED9F6BFBCE62EDF1B3B4840EC2EE31E60C1FFBF9A8B5A06697848406EB02119005BFDBFFFADA214FC9F48404F9A5062761C01C08D5D8AF81AC54840A741DBB6F46F00C0A794563BC7F54840D46E69FC7BADF0BF9209921479EB48402A9E9376DC91EDBF68770E2D80D74840B8C51A3A923EF2BFBEF8954D85A74840'), -( '56', 'Morbihan', '0106000020E6100000010000000103000000010000000C000000E4B620E4E2240BC0FEB45B38BAF94740774E5378B7E00DC0294E828F6A0E484023BF45F9F9A108C0EF1C8319FE1A4840924F13ABE30605C0DDE55037530448403BAC61A27A5A03C01624267C26164840E7EF28B6614800C0173C765F9AEA47405F7B83FB68CA00C05FE21C1748C44740A5DDC755555505C0893CEE00F4B4474001D6FE43EF1309C0D9A0B858288A47407DF2ABA3C2A70BC04461A6FF1395474076842435CE2C0DC03F619A1AF2B44740E4B620E4E2240BC0FEB45B38BAF94740'), -( '76', 'Seine-Maritime', '0106000020E610000002000000010300000001000000040000008E638ED8E3CACEBF536AA2D115C548404212DBEC4024CFBF2BC3A669A6C4484045FFB2E965CBD9BF9DA73A1352C748408E638ED8E3CACEBF536AA2D115C548400103000000010000000A000000D5D11008201BF63F23108C92690849407260B3DA6796FC3FE978E8E187E14840D8044B18D81CFB3F3EC68AE392B2484049707D32F32EF63FC4BE92224CBB48408A384019E105F03F3FF865781CA0484038CA80687AAFE93FF5488C5CBBB648408E638ED8E3CACEBF536AA2D115C548408D9F14B1CCA3A13FA0A84B89F8F04840BEC89B0A72E0F23F795C9EE6BA244940D5D11008201BF63F23108C9269084940'), -( '85', 'Vendée', '0106000020E6100000010000000103000000010000000C000000CCFA46BAAEE402C0D31D98E8888547403F112A04B2C6F8BFF4E9EE09176E47402CE76995FBEEF7BF80D752E251854740E8E04BEC8A55F7BF62CB69ED8676474091F1F6B30253F4BF28873EC7E08A47406FD115C8F5BBEDBFCE9F91CB098147405848D9378138E1BFB8C51B0A74314740E596F31827A6F3BF5E6C8C63C51E4740A4294FF0A33900C0748141BA5C2A4740718E9839307C05C0735DA8E2F853474072EA8423576505C04A45F748E9814740CCFA46BAAEE402C0D31D98E888854740'); diff --git a/datascience/tests/test_data/remote_database/V666.5__Reset_dummy_facade_areas_subdivided.sql b/datascience/tests/test_data/remote_database/V666.5__Reset_dummy_facade_areas_subdivided.sql deleted file mode 100644 index 60ae42bfda..0000000000 --- a/datascience/tests/test_data/remote_database/V666.5__Reset_dummy_facade_areas_subdivided.sql +++ /dev/null @@ -1,7 +0,0 @@ -DELETE FROM facade_areas_subdivided; - -INSERT INTO facade_areas_subdivided ( - facade, geometry -) VALUES -('Facade A', ST_GeomFromText('MULTIPOLYGON (((10.0 45.0, -10.0 45.0, -10.0 0.0, 10.0 0.0, 10.0 45.0)))', 4326)), -('Facade B', ST_GeomFromText('MULTIPOLYGON (((10.0 45.0, -10.0 45.0, -10.0 50.0, 10.0 50.0, 10.0 45.0)))', 4326)); diff --git a/datascience/tests/test_data/remote_database/V666.6__Reset_dummy_emails_sent_to_control_units.sql b/datascience/tests/test_data/remote_database/V666.6__Reset_dummy_emails_sent_to_control_units.sql deleted file mode 100644 index 0d7347a1d0..0000000000 --- a/datascience/tests/test_data/remote_database/V666.6__Reset_dummy_emails_sent_to_control_units.sql +++ /dev/null @@ -1,3 +0,0 @@ -DELETE FROM public.emails_sent_to_control_units; - -ALTER SEQUENCE emails_sent_to_control_units_id_seq RESTART WITH 1; \ No newline at end of file diff --git a/datascience/tests/test_data/remote_database/V666.999__Refresh_materialized_views.sql b/datascience/tests/test_data/remote_database/V666.999__Refresh_materialized_views.sql deleted file mode 100644 index d4627c7a02..0000000000 --- a/datascience/tests/test_data/remote_database/V666.999__Refresh_materialized_views.sql +++ /dev/null @@ -1,2 +0,0 @@ -REFRESH MATERIALIZED VIEW public.analytics_actions; -REFRESH MATERIALIZED VIEW public.analytics_surveillance_density_map; \ No newline at end of file diff --git a/datascience/tests/test_data/vessel_xml/vessel_repository.xml b/datascience/tests/test_data/vessel_xml/vessel_repository.xml deleted file mode 100644 index 216c3bbaf1..0000000000 --- a/datascience/tests/test_data/vessel_xml/vessel_repository.xml +++ /dev/null @@ -1,92 +0,0 @@ - - -
- 001 - 001 - 2025-09-22T16:00:00 - 2 -
- - - - 1 - 1 - - 1111111 - A - PRO - No - - 2019-05-29T00:00:00.000 - 1234567 - 123456789 - 0000001 - ShipName1 - FRA - CHERBOURG - 999999 - - 2019-05-29T00:00:00.000 - Porte-Conteneur - UICKSILVER - 32 - - 2019-05-29T00:00:00.000 - RÉFÉRENTIEL NAVIRES DAM - NOM 1 - PRENOM 1 - 1977-08-19 - 17 AVENUE DESAVENUES 14000 CAEN - 0987654321 - email1@gmail.com - FRA - COMPANY 1 - 93.29Z - 1234 - 2019-05-29 - - - - - - - 1 - 1 - - 2222222 - A - PLA - No - - 2019-05-29T00:00:00.000 - 7654321 - 987654321 - 0000002 - ShipName2 - FRA - DZAOUDZI - 888888 - - 2019-05-29T00:00:00.000 - Navire a passagers - 9.6 - - 2019-05-29T00:00:00.000 - RÉFÉRENTIEL NAVIRES DAM - NOM 2 - PRENOM 2 - 1977-08-19 - 17 RUE DESRUES 14000 CAEN - 0123456789 - email2@gmail.com - FRA - COMPANY 2 - 93.87Z - 5678 - 2019-05-29 - - - - - -
\ No newline at end of file diff --git a/datascience/tests/test_data/vessel_xml/vessel_repository.xsd b/datascience/tests/test_data/vessel_xml/vessel_repository.xsd deleted file mode 100644 index 447c1a0bbf..0000000000 --- a/datascience/tests/test_data/vessel_xml/vessel_repository.xsd +++ /dev/null @@ -1,675 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/datascience/tests/test_data/zip_files/amp_areas.zip b/datascience/tests/test_data/zip_files/amp_areas.zip deleted file mode 100644 index 455e349c9c..0000000000 Binary files a/datascience/tests/test_data/zip_files/amp_areas.zip and /dev/null differ diff --git a/datascience/tests/test_db_config.py b/datascience/tests/test_db_config.py deleted file mode 100644 index ec462fd30c..0000000000 --- a/datascience/tests/test_db_config.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -import unittest -from unittest.mock import patch - -from src.db_config import create_engine - - -class TestDBConfig(unittest.TestCase): - @patch("src.db_config.sa") - def test_create_engine(self, mock_sa): - os.environ["MONITORFISH_LOCAL_CLIENT"] = "postgresql" - os.environ["MONITORFISH_LOCAL_HOST"] = "12.34.567.89" - os.environ["MONITORFISH_LOCAL_PORT"] = "0000" - os.environ["MONITORFISH_LOCAL_NAME"] = "db_name" - os.environ["MONITORFISH_LOCAL_USER"] = "db_user" - os.environ["MONITORFISH_LOCAL_PWD"] = "db_pwd" - - create_engine("monitorfish_local") - mock_sa.create_engine.assert_called_once_with( - "postgresql://db_user:db_pwd@12.34.567.89:0000/db_name" - ) - - os.environ.pop("MONITORFISH_LOCAL_CLIENT") - os.environ.pop("MONITORFISH_LOCAL_HOST") - os.environ.pop("MONITORFISH_LOCAL_PORT") - os.environ.pop("MONITORFISH_LOCAL_NAME") - os.environ.pop("MONITORFISH_LOCAL_USER") - os.environ.pop("MONITORFISH_LOCAL_PWD") - - with self.assertRaises(KeyError): - create_engine("monitorfish_local") diff --git a/datascience/tests/test_pipeline/__init__.py b/datascience/tests/test_pipeline/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/tests/test_pipeline/test_flows/__init__.py b/datascience/tests/test_pipeline/test_flows/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/tests/test_pipeline/test_flows/test_amp_cacem.py b/datascience/tests/test_pipeline/test_flows/test_amp_cacem.py deleted file mode 100644 index 3526c3e1e1..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_amp_cacem.py +++ /dev/null @@ -1,161 +0,0 @@ -import pandas as pd -import prefect -import pytest - -from src.pipeline.flows.amp_cacem import load_new_amps, update_amps -from src.pipeline.generic_tasks import delete_rows, load -from src.read_query import read_query - - -@pytest.fixture -def old_amp() -> pd.DataFrame: - return pd.DataFrame( - { - "id": [1, 2, 3, 4], - "geom": [ - "0106000020E610000001000000010300000001000000040000001EA36CE84A6F04C028FCC" - "F619D7F47407B5A4C4F4F6904C06878344D997F4740906370C20E6A04C050111641647F47" - "401EA36CE84A6F04C028FCCF619D7F4740", - "0106000020E61000000100000001030000000100000004000000508B8D44B1B304C014238" - "1B3F47F4740A374D56D789004C0C0F2BF049B7F474033F02B2558B104C0CCA0D40BEE7E47" - "40508B8D44B1B304C0142381B3F47F4740", - "0106000020E61000000100000001030000000100000004000000D2204A8709EBE33F541AC" - "4E69B024940B8BC1FBE94F2E33F387D124AAF02494021642107D81FE43F387D124AAF0249" - "40D2204A8709EBE33F541AC4E69B024940", - "0106000020E61000000100000001030000000100000004000000F57994631533F2BFE2B98" - "CD5455446407A715E737969F3BFEAD7CEDEB655464036ED5A29A137F4BF97F69352CC3446" - "40F57994631533F2BFE2B98CD545544640", - ], - "mpa_oriname": [ - "Calanques - aire d'adhésion", - "dunes, forêt et marais d'Olonne", - "dunes, forêt et marais d'Olonne'", - "estuaire de la Bidassoa et baie de Fontarabie", - ], - "des_desigfr": [ - "Parc national (aire d'adhésion)", - "Zone de protection spéciale (N2000, DO)", - "Zone spéciale de conservation (N2000, DHFF)", - "Zone de protection spéciale (N2000, DO)", - ], - "mpa_type": [ - "Parc national", - "Natura 2000", - "Parc naturel marin", - "Réserve naturelle" - ], - "ref_reg": [ - "arrêté 1", - "arrêté 2", - "arrêté 3", - "arrêté 4", - ], - "url_legicem": [ - "http://dummy_url_1", - "http://dummy_url_2", - "http://dummy_url_3", - "http://dummy_url_4", - ], - "row_hash": [ - "cacem_row_hash_1", - "cacem_row_hash_2", - "cacem_row_hash_3", - "cacem_row_hash_4_new", - ], - } - ) - -@pytest.fixture -def new_amp() -> pd.DataFrame: - return pd.DataFrame( - { - "id": [1, 2, 3, 4], - "geom": [ - "0106000020E610000001000000010300000001000000040000001EA36CE84A6F04C028FCC" - "F619D7F47407B5A4C4F4F6904C06878344D997F4740906370C20E6A04C050111641647F47" - "401EA36CE84A6F04C028FCCF619D7F4740", - "0106000020E61000000100000001030000000100000004000000508B8D44B1B304C014238" - "1B3F47F4740A374D56D789004C0C0F2BF049B7F474033F02B2558B104C0CCA0D40BEE7E47" - "40508B8D44B1B304C0142381B3F47F4740", - "0106000020E61000000100000001030000000100000004000000D2204A8709EBE33F541AC" - "4E69B024940B8BC1FBE94F2E33F387D124AAF02494021642107D81FE43F387D124AAF0249" - "40D2204A8709EBE33F541AC4E69B024940", - "0106000020E61000000100000001030000000100000004000000F57994631533F2BFE2B98" - "CD5455446407A715E737969F3BFEAD7CEDEB655464036ED5A29A137F4BF97F69352CC3446" - "40F57994631533F2BFE2B98CD545544640", - ], - "mpa_oriname": [ - "Calanques - aire d'adhésion", - "dunes, forêt et marais d'Olonne", - "dunes, forêt et marais d'Olonne'", - "estuaire de la Bidassoa et baie de Fontarabie", - ], - "des_desigfr": [ - "Parc national (aire d'adhésion)", - "Zone de protection spéciale (N2000, DO)", - "Zone spéciale de conservation (N2000, DHFF)", - "Zone de protection spéciale (N2000, DO)", - ], - "mpa_type": [ - "Parc national", - "Natura 2000", - "Parc naturel marin", - "Réserve naturelle" - ], - "ref_reg": [ - "arrêté 1", - "arrêté 2_updated", - "arrêté 3", - "arrêté 4", - ], - "url_legicem": [ - "https://dummy_url_1", - "https://dummy_url_2", - "https://dummy_url_3", - "https://dummy_url_4", - ], - "row_hash": [ - "cacem_row_hash_1", - "cacem_row_hash_2", - "cacem_row_hash_3", - "cacem_row_hash_4_new", - ], - } - ) - - -def test_load_new_amps(reset_test_data, old_amp): - load_new_amps.run(old_amp) - loaded_amps = read_query( - "monitorenv_remote", - """SELECT id, geom, - mpa_oriname, des_desigfr, - mpa_type, ref_reg, - url_legicem, row_hash - FROM amp_cacem - ORDER BY id""" - ) - pd.testing.assert_frame_equal(loaded_amps, old_amp) - - -def test_update_new_amps(reset_test_data, new_amp, old_amp): - load( - old_amp, - table_name="amp_cacem", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="append", - ) - - update_amps.run(new_amp) - updated_amps = read_query( - "monitorenv_remote", - """SELECT id, geom, - mpa_oriname, des_desigfr, - mpa_type, ref_reg, - url_legicem, row_hash - FROM amp_cacem - ORDER BY id""" - ) - pd.testing.assert_frame_equal(updated_amps, new_amp) \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/test_amp_ofb.py b/datascience/tests/test_pipeline/test_flows/test_amp_ofb.py deleted file mode 100644 index 447ac353e2..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_amp_ofb.py +++ /dev/null @@ -1,203 +0,0 @@ -import geopandas as gpd -from geopandas.testing import assert_geodataframe_equal -import pytest - -from src.pipeline.flows.amp_ofb import AMP_AREAS_COLUMNS, load_amp_areas, transform_amp_areas - -from src.read_query import read_query -from tests.test_pipeline.test_shared_tasks.test_geometries import make_square_multipolygon - -@pytest.fixture -def amp_areas_from_ofb() -> gpd.GeoDataFrame: - return gpd.GeoDataFrame( - { - "mpa_id": [ 3.0, 4, 7, 92, 735], - "mpa_pid": [1.0, 2, 3, 4, 7], - "gid": [1.0, 2, 3, 4, 7], - "des_id": [1.0, 2, 3, 4, 7], - "mpa_status": ["designated", "designated", "proposed", "proposed", "proposed"], - "mpa_name": [ - "AMP 1 updated", - "AMP 2", - "AMP 3", - "AMP 4", - "AMP 7", - ], - "mpa_oriname": [ - "Nom AMP 1 updated", - "Nom AMP 2", - "Nom AMP 3", - "Nom AMP 4", - "Nom AMP 5", - ], - "des_desigfr": [ - "Secteur 1 updated", - "Secteur 2", - "Zone A", - "Zone B", - "Zone C", - ], - "des_desigtype": [ - "External regulation updated", - "some regulation, some other regulation", - None, - "Med regulation", - "Dead link regulation", - ], - "mpa_datebegin": [ - "2024-01-01", - "2001-01-01", - "2002-01-01", - "2003-01-01", - "2004-01-01", - ], - "mpa_statusyr": [2024.0, 2001, 2002, 2003, 2004], - "mpa_wdpaid": [106767.0, 555526886, 555526887, 555526888, 555526889], - "mpa_wdpapid": ["WDPA 1", "WDPA 2", "WDPA 3", "WDPA 4", "WDPA 5"], - "mpa_mnhnid": ["FR3600138", "FR1100014", None, "FR11014", "FR11330014"], - "mpa_marine": [1.0, 0, 1, None, 1], - "mpa_url": ["URL 1", "URL 2", "URL 3", "URL 4", None], - "mpa_calcarea": [100.1, 200, 300, 400, 500], - "mpa_calcmarea": [100.1, 200, 300, 400, 500], - "mpa_reparea": [100.1, 200, 300, 400, 500], - "mpa_repmarea": [100.1, 200, 300, 400, 500], - "mpa_updatewhen": [ - "2024-01-01", - "2001-01-01", - "2002-01-01", - "2003-01-01", - "2004-01-01", - ], - "iucn_idiucn": ["IUCN 1 updaed", "IUCN 2", "IUCN 3", "IUCN 4", "IUCN 5"], - "subloc_code": ["Subloc 1 updated", "Subloc 2", "Subloc 3", "Subloc 4", "Subloc 5"], - "subloc_name": ["Subloc 1 updated", "Subloc 2", "Subloc 3", "Subloc 4", "Subloc 5"], - "country_piso3": ["FRA updated", "FRA", "FRA", "FRA", "FRA"], - "country_iso3": ["FRA updated", "FRA", "FRA", "FRA", "FRA"], - "country_iso3namefr": ["France updated", "France", "France", "France", "France"], - "geom": [ - make_square_multipolygon(0, 0, 10, 10), - make_square_multipolygon(120, -20, 15, 10), - make_square_multipolygon(-60, 10, 5, 10), - make_square_multipolygon(-10, 45, 180, 5), - make_square_multipolygon(-110, 60, 10, 10), - ], - - }, - crs="EPSG:4326", - geometry="geom", - ) - -@pytest.fixture -def amp_areas_after_upsert() -> gpd.GeoDataFrame: - return gpd.GeoDataFrame( - { - "mpa_id": [ 3, 4, 7, 92, 735], - "mpa_pid": [1, 2, 3, 4, 7], - "gid": [1, 2, 3, 4, 7], - "des_id": [1, 2, 3, 4, 7], - "mpa_status": ["designated", "designated", "proposed", "proposed", "proposed"], - "mpa_name": [ - "AMP 1 updated", - "AMP 2", - "AMP 3", - "AMP 4", - "AMP 7", - ], - "mpa_oriname": [ - "Nom AMP 1 updated", - "Nom AMP 2", - "Nom AMP 3", - "Nom AMP 4", - "Nom AMP 5", - ], - "des_desigfr": [ - "Secteur 1 updated", - "Secteur 2", - "Zone A", - "Zone B", - "Zone C", - ], - "des_desigtype": [ - "External regulation updated", - "some regulation, some other regulation", - None, - "Med regulation", - "Dead link regulation", - ], - "mpa_datebegin": [ - "2024-01-01", - "2001-01-01", - "2002-01-01", - "2003-01-01", - "2004-01-01", - ], - "mpa_statusyr": [2024, 2001, 2002, 2003, 2004], - "mpa_wdpaid": [106767, 555526886, 555526887, 555526888, 555526889], - "mpa_wdpapid": ["WDPA 1", "WDPA 2", "WDPA 3", "WDPA 4", "WDPA 5"], - "mpa_mnhnid": ["FR3600138", "FR1100014", None, "FR11014", "FR11330014"], - "mpa_marine": [1, 0, 1, None, 1], - "mpa_url": ["URL 1", "URL 2", "URL 3", "URL 4", None], - "mpa_calcarea": [100.1, 200, 300, 400, 500], - "mpa_calcmarea": [100.1, 200, 300, 400, 500], - "mpa_reparea": [100.1, 200, 300, 400, 500], - "mpa_repmarea": [100.1, 200, 300, 400, 500], - "mpa_updatewhen": [ - "2024-01-01", - "2001-01-01", - "2002-01-01", - "2003-01-01", - "2004-01-01", - ], - "iucn_idiucn": ["IUCN 1 updaed", "IUCN 2", "IUCN 3", "IUCN 4", "IUCN 5"], - "subloc_code": ["Subloc 1 updated", "Subloc 2", "Subloc 3", "Subloc 4", "Subloc 5"], - "subloc_name": ["Subloc 1 updated", "Subloc 2", "Subloc 3", "Subloc 4", "Subloc 5"], - "country_piso3": ["FRA updated", "FRA", "FRA", "FRA", "FRA"], - "country_iso3": ["FRA updated", "FRA", "FRA", "FRA", "FRA"], - "country_iso3namefr": ["France updated", "France", "France", "France", "France"], - "geom": [ - make_square_multipolygon(0, 0, 10, 10), - make_square_multipolygon(120, -20, 15, 10), - make_square_multipolygon(-60, 10, 5, 10), - make_square_multipolygon(-10, 45, 180, 5), - make_square_multipolygon(-110, 60, 10, 10), - ], - "mpa_type": [None, None, None, None, "MPA Type 1"], - "ref_reg": [ None, None, None, None, "Résumé reg"], - "url_legicem": [None, None, None, None, "url legicem 1"], - - }, - crs="EPSG:4326", - geometry="geom", - ) - - -def test_transform_amp_areas(amp_areas_from_ofb): - - downloaded_amps = gpd.read_file("tests/test_data/zip_files/amp_areas.zip") - transformed_amp = transform_amp_areas.run(downloaded_amps) - - assert amp_areas_from_ofb.crs == transformed_amp.crs - - columns_to_load = AMP_AREAS_COLUMNS - assert amp_areas_from_ofb[columns_to_load].columns.equals(transformed_amp[columns_to_load].columns) - - -def test_load_amp_areas(create_cacem_tables, amp_areas_from_ofb, amp_areas_after_upsert): - load_amp_areas.run(amp_areas_from_ofb) - loaded_amp = read_query( - db = "monitorenv_remote", - query= "SELECT mpa_id, mpa_pid, gid, des_id, mpa_status, " - "mpa_name, mpa_oriname, des_desigfr, des_desigtype, " - "mpa_datebegin, mpa_statusyr, mpa_wdpaid, mpa_wdpapid, " - "mpa_mnhnid, mpa_marine, mpa_url, mpa_calcarea, " - "mpa_calcmarea, mpa_reparea, mpa_repmarea, mpa_updatewhen, " - "iucn_idiucn, subloc_code, subloc_name, country_piso3, " - "country_iso3, country_iso3namefr, geom, mpa_type, ref_reg, url_legicem " - "FROM prod.\"Aires marines protégées\" " - "ORDER BY mpa_id", - backend="geopandas", - geom_col="geom", - crs=4326) - - assert_geodataframe_equal(amp_areas_after_upsert, loaded_amp) - diff --git a/datascience/tests/test_pipeline/test_flows/test_beaches.py b/datascience/tests/test_pipeline/test_flows/test_beaches.py deleted file mode 100644 index 175ac12630..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_beaches.py +++ /dev/null @@ -1,19 +0,0 @@ -from geopandas.testing import assert_geodataframe_equal - -from src.pipeline.flows.beaches import extract_beaches, load_beaches -from src.read_query import read_query - -def test_load_localized_areas(create_cacem_tables): - beaches = extract_beaches.run() - assert beaches.shape[0] == 1 - - load_beaches.run(beaches) - imported_beaches = read_query( - db="monitorenv_remote", - query="SELECT id, name, insee, official_name, geom, postcode FROM beaches", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - - assert_geodataframe_equal(beaches, imported_beaches) \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/test_competence_cross_areas.py b/datascience/tests/test_pipeline/test_flows/test_competence_cross_areas.py deleted file mode 100644 index f69c6e2885..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_competence_cross_areas.py +++ /dev/null @@ -1,24 +0,0 @@ -from geopandas.testing import assert_geodataframe_equal - -from src.pipeline.flows.competence_cross_areas import extract_competence_cross_areas, load_competence_cross_areas, flow -from src.read_query import read_query - - -def test_load_competence_cross_areas(create_cacem_tables): - competence_cross_areas = extract_competence_cross_areas.run() - assert competence_cross_areas.shape[0] == 1 - - load_competence_cross_areas.run(competence_cross_areas) - imported_competence_cross_areas = read_query( - db="monitorenv_remote", - query="SELECT id, geom, name, description, \"timestamp\", \"begin\", \"end\", altitude_mode, tessellate, extrude, visibility, draw_order, icon FROM competence_cross_areas", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - - assert_geodataframe_equal(competence_cross_areas, imported_competence_cross_areas) - -def test_flow_competence_cross_areas(create_cacem_tables): - state = flow.run() - assert state.is_successful() diff --git a/datascience/tests/test_pipeline/test_flows/test_control_objectives.py b/datascience/tests/test_pipeline/test_flows/test_control_objectives.py deleted file mode 100644 index 929d2de659..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_control_objectives.py +++ /dev/null @@ -1,28 +0,0 @@ -from src.pipeline.flows.control_objectives import flow -from src.read_query import read_query -from tests.mocks import mock_check_flow_not_running - -flow.replace( - flow.get_tasks("check_flow_not_running")[0], mock_check_flow_not_running -) - - -def test_flow(reset_test_data): - - query = ( - "SELECT * FROM control_objectives ORDER BY target_number_of_controls" - ) - initial_control_objectives = read_query("monitorenv_remote", query) - - flow.schedule = None - state = flow.run(file_name="dummy_control_objectives.csv") - assert state.is_successful() - - final_control_objectives = read_query("monitorenv_remote", query) - - assert len(initial_control_objectives) == 0 - assert len(final_control_objectives) == 16 - assert (final_control_objectives.year == 2023).all() - assert ( - final_control_objectives.target_number_of_controls == range(1, 17) - ).all() diff --git a/datascience/tests/test_pipeline/test_flows/test_email_actions_to_units.py b/datascience/tests/test_pipeline/test_flows/test_email_actions_to_units.py deleted file mode 100644 index 48a5c9f9e3..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_email_actions_to_units.py +++ /dev/null @@ -1,783 +0,0 @@ -import dataclasses -from datetime import datetime -from email.message import EmailMessage -from smtplib import SMTPDataError -from typing import List -from unittest.mock import patch -from uuid import UUID - -import pandas as pd -import pytest -from jinja2 import Template - -from config import ( - CACEM_EMAIL_ADDRESS, - MONITORENV_SENDER_EMAIL_ADDRESS, - TEST_DATA_LOCATION, -) -from src.pipeline.entities.actions_emailing import ( - ControlUnit, - ControlUnitActions, - ControlUnitActionsSentMessage, -) -from src.pipeline.flows.email_actions_to_units import ( - control_unit_actions_list_to_df, - create_email, - extract_all_control_units, - extract_control_units, - extract_env_actions, - flow, - get_actions_period, - get_control_unit_ids, - get_template, - load_emails_sent_to_control_units, - render, - send_env_actions_email, - to_control_unit_actions, -) -from src.pipeline.helpers.dates import Period -from src.read_query import read_query -from tests.mocks import mock_check_flow_not_running - -flow.replace( - flow.get_tasks("check_flow_not_running")[0], mock_check_flow_not_running -) - - -@pytest.fixture -def expected_env_actions() -> pd.DataFrame: - - return pd.DataFrame( - { - "id": [ - UUID("88713755-3966-4ca4-ae18-10cab6249485"), - UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), - UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), - UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), - UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), - ], - "action_start_datetime_utc": [ - datetime( - year=2022, - month=6, - day=28, - hour=13, - minute=59, - second=20, - microsecond=176000, - ), - datetime( - year=2022, - month=11, - day=24, - hour=20, - minute=31, - second=41, - microsecond=719000, - ), - datetime( - year=2022, - month=11, - day=24, - hour=20, - minute=31, - second=41, - microsecond=719000, - ), - datetime( - year=2022, - month=11, - day=20, - hour=20, - minute=31, - second=41, - microsecond=719000, - ), - datetime( - year=2022, - month=11, - day=20, - hour=20, - minute=31, - second=41, - microsecond=719000, - ), - ], - "action_end_datetime_utc": [ - datetime( - year=2022, - month=7, - day=5, - hour=19, - minute=59, - second=20, - microsecond=176000, - ), - pd.NaT, - pd.NaT, - datetime( - year=2022, - month=11, - day=20, - hour=23, - minute=31, - second=41, - microsecond=719000, - ), - datetime( - year=2022, - month=11, - day=20, - hour=23, - minute=31, - second=41, - microsecond=719000, - ), - ], - "mission_type": ["SEA", "SEA", "SEA", "SEA", "SEA"], - "action_type": [ - "SURVEILLANCE", - "CONTROL", - "CONTROL", - "SURVEILLANCE", - "SURVEILLANCE", - ], - "control_unit_id": [10019, 10018, 10019, 10018, 10019], - "action_facade": [ - "Hors façade", - "Hors façade", - "Hors façade", - "Hors façade", - "Hors façade", - ], - "action_department": [ - "Hors département", - "Hors département", - "Hors département", - "Hors département", - "Hors département", - ], - "infraction": [None, False, False, None, None], - "number_of_controls": [None, 0.0, 0.0, None, None], - "surveillance_duration": [174.0, None, None, 3.0, 3.0], - "is_late_update": [True, False, False, False, False], - "themes": [ - { - "Culture marine": [ - "Prescriptions réglementaires des concessions d'exploitation de culture marine" - ], - "Police des espèces protégées et de leurs habitats (faune et flore)": [ - "Dérogations concernant les espèces protégées", - "Détention d'espèces protégées", - ], - }, - {"Aucun thème": ["Aucun sous-thème"]}, - {"Aucun thème": ["Aucun sous-thème"]}, - {"EIN2000": ["Aucun sous-thème"]}, - {"EIN2000": ["Aucun sous-thème"]}, - ], - "longitude": [None, -3.0193, -3.0193, None, None], - "latitude": [None, 48.12065, 48.12065, None, None], - } - ) - - -@pytest.fixture -def expected_control_unit_ids() -> List[int]: - return [10018, 10019] - - -@pytest.fixture -def expected_control_units() -> pd.DataFrame: - return pd.DataFrame( - { - "control_unit_id": [10018, 10019], - "control_unit_name": ["P602 Verdon", "BN Toulon"], - "email_addresses": [ - ["diffusion.p602@email.fr", "diffusion_bis.p602@email.fr"], - ["bn_toulon@email.fr"], - ], - } - ) - - -@pytest.fixture -def expected_all_control_units() -> pd.DataFrame: - return pd.DataFrame( - { - "control_unit_id": [10002, 10018, 10019], - "control_unit_name": ["DML – DDTM 59", "P602 Verdon", "BN Toulon"], - "email_addresses": [ - ["dml59@surveillance.fr"], - ["diffusion.p602@email.fr", "diffusion_bis.p602@email.fr"], - ["bn_toulon@email.fr"], - ], - } - ) - - -@pytest.fixture -def sample_control_unit_actions() -> ControlUnitActions: - return ControlUnitActions( - control_unit=ControlUnit( - control_unit_id=13, - control_unit_name="Nom de l'unité", - email_addresses=["email@email.com", "email2@email.com"], - ), - period=Period( - start=datetime(2020, 6, 23, 0, 0, 0), - end=datetime(2020, 5, 6, 18, 45, 6), - ), - controls=pd.DataFrame( - { - "id": [UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2")], - "action_start_datetime_utc": [ - datetime(2022, 11, 24, 20, 31, 41, 719000) - ], - "action_end_datetime_utc": [pd.NaT], - "mission_type": ["SEA"], - "action_type": ["CONTROL"], - "control_unit_id": [10019], - "action_facade": ["Hors façade"], - "action_department": ["Hors département"], - "infraction": [False], - "number_of_controls": [0.0], - "surveillance_duration": [None], - "themes": [{"Aucun thème": ["Aucun sous-thème"]}], - "longitude": [-3.0193], - "latitude": [48.12065], - } - ), - surveillances=pd.DataFrame( - { - "id": [ - UUID("88713755-3966-4ca4-ae18-10cab6249485"), - UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), - ], - "action_start_datetime_utc": [ - datetime(2022, 11, 28, 13, 59, 20, 176000), - datetime(2022, 11, 20, 20, 31, 41, 719000), - ], - "action_end_datetime_utc": [ - datetime(2022, 12, 5, 19, 59, 20, 176000), - datetime(2022, 11, 20, 23, 31, 41, 719000), - ], - "mission_type": ["SEA", "SEA"], - "action_type": ["SURVEILLANCE", "SURVEILLANCE"], - "control_unit_id": [10019, 10019], - "action_facade": ["Hors façade", "Hors façade"], - "action_department": ["Hors département", "Hors département"], - "infraction": [None, None], - "number_of_controls": [None, None], - "surveillance_duration": [174.0, 3.0], - "themes": [ - { - "Culture marine": ["Implantation"], - "Police des espèces protégées et de leurs habitats (faune et flore)": [ - "Dérogations concernant les espèces protégées", - "Détention d'espèces protégées", - ], - }, - { - "Activités et manifestations soumises à évaluation d’incidence Natura 2000": [ - "Aucun sous-thème" - ] - }, - ], - "longitude": [None, None], - "latitude": [None, None], - } - ), - late_controls=pd.DataFrame( - { - "id": [UUID("394a0c7e-e8df-4a90-bb5a-35594a0db694")], - "action_start_datetime_utc": [ - datetime(2022, 11, 24, 20, 31, 41, 719000) - ], - "action_end_datetime_utc": [pd.NaT], - "mission_type": ["SEA"], - "action_type": ["CONTROL"], - "control_unit_id": [10019], - "action_facade": ["Hors façade"], - "action_department": ["Hors département"], - "infraction": [False], - "number_of_controls": [0.0], - "surveillance_duration": [None], - "themes": [{"Aucun thème": ["Aucun sous-thème"]}], - "longitude": [-1.0193], - "latitude": [28.12065], - } - ), - late_surveillances=pd.DataFrame( - { - "id": [ - UUID("a1e11c27-faab-49f6-bc2e-d7ffa0864ca2"), - ], - "action_start_datetime_utc": [ - datetime(2022, 11, 28, 13, 59, 20, 176000), - ], - "action_end_datetime_utc": [ - datetime(2022, 12, 5, 19, 59, 20, 176000), - ], - "mission_type": ["SEA"], - "action_type": ["SURVEILLANCE"], - "control_unit_id": [10019], - "action_facade": ["Hors façade"], - "action_department": ["Hors département"], - "infraction": [None], - "number_of_controls": [None], - "surveillance_duration": [54.0], - "themes": [ - { - "Culture marine": ["Implantation"], - "Police des espèces protégées et de leurs habitats (faune et flore)": [ - "Dérogations concernant les espèces protégées", - "Détention d'espèces protégées", - ], - }, - ], - "longitude": [None], - "latitude": [None], - } - ), - ) - - -@pytest.fixture -def sample_control_unit_actions_without_actions( - sample_control_unit_actions, -) -> ControlUnitActions: - return dataclasses.replace( - sample_control_unit_actions, - controls=sample_control_unit_actions.controls.head(0), - surveillances=sample_control_unit_actions.surveillances.head(0), - late_controls=sample_control_unit_actions.late_controls.head(0), - late_surveillances=sample_control_unit_actions.late_surveillances.head( - 0 - ), - ) - - -@pytest.fixture -def control_unit_actions_sent_messages() -> List[ - ControlUnitActionsSentMessage -]: - - return [ - ControlUnitActionsSentMessage( - control_unit_id=13, - control_unit_name="Nom de l'unité", - email_address="email@email.com", - sending_datetime_utc=datetime(2024, 3, 19, 14, 37, 24, 497093), - actions_min_datetime_utc=datetime(2020, 6, 23, 0, 0), - actions_max_datetime_utc=datetime(2020, 5, 6, 18, 45, 6), - number_of_actions=2, - success=True, - error_code=None, - error_message=None, - ), - ControlUnitActionsSentMessage( - control_unit_id=13, - control_unit_name="Nom de l'unité", - email_address="email2@email.com", - sending_datetime_utc=datetime(2024, 3, 19, 14, 37, 24, 497093), - actions_min_datetime_utc=datetime(2020, 6, 23, 0, 0), - actions_max_datetime_utc=datetime(2020, 5, 6, 18, 45, 6), - number_of_actions=2, - success=False, - error_code=550, - error_message="Email cound not be sent.", - ), - ] - - -@pytest.fixture -def control_unit_actions_sent_messages_df() -> pd.DataFrame: - return pd.DataFrame( - { - "control_unit_id": [13, 13], - "control_unit_name": ["Nom de l'unité", "Nom de l'unité"], - "email_address": ["email@email.com", "email2@email.com"], - "sending_datetime_utc": [ - datetime( - year=2024, - month=3, - day=19, - hour=14, - minute=37, - second=24, - microsecond=497093, - ), - datetime( - year=2024, - month=3, - day=19, - hour=14, - minute=37, - second=24, - microsecond=497093, - ), - ], - "actions_min_datetime_utc": [ - datetime( - year=2020, month=6, day=23, hour=00, minute=00, second=00 - ), - datetime( - year=2020, month=6, day=23, hour=00, minute=00, second=00 - ), - ], - "actions_max_datetime_utc": [ - datetime( - year=2020, month=5, day=6, hour=18, minute=45, second=6 - ), - datetime( - year=2020, month=5, day=6, hour=18, minute=45, second=6 - ), - ], - "number_of_actions": [2, 2], - "success": [True, False], - "error_code": [None, 550.0], - "error_message": [None, "Email cound not be sent."], - } - ) - - -@pytest.fixture -def expected_email(sample_control_unit_actions) -> EmailMessage: - - email = EmailMessage() - email["Subject"] = "Bilan hebdomadaire contrôle de l'environnement marin" - email["From"] = MONITORENV_SENDER_EMAIL_ADDRESS - email["To"] = ", ".join( - sample_control_unit_actions.control_unit.email_addresses - ) - email["Reply-To"] = CACEM_EMAIL_ADDRESS - email.set_content( - "Bonjour ceci est un email test.\n", subtype="html" - ) - - return email - - -def test_get_actions_period(): - period = get_actions_period.run( - utcnow=datetime(2021, 2, 21, 16, 10, 0), - start_days_ago=5, - end_days_ago=2, - ) - assert period == Period( - start=datetime(2021, 2, 16, 0, 0), end=datetime(2021, 2, 20, 0, 0) - ) - - -def test_extract_env_actions(reset_test_data, expected_env_actions): - # Dates with some data - actions = extract_env_actions.run( - period=Period(start=datetime(2022, 10, 1), end=datetime(2022, 12, 6)) - ) - assert len(actions) == 5 - assert set(actions.id.unique()) == set( - { - UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), - UUID("88713755-3966-4ca4-ae18-10cab6249485"), - UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), - } - ) - pd.testing.assert_frame_equal(actions, expected_env_actions) - - # Dates without data - actions = extract_env_actions.run( - period=Period(start=datetime(2023, 10, 1), end=datetime(2023, 12, 6)) - ) - assert len(actions) == 0 - - -def test_get_control_unit_ids(expected_env_actions, expected_control_unit_ids): - ids = get_control_unit_ids.run(expected_env_actions) - assert ids == expected_control_unit_ids - id_types = set(map(type, ids)) - - # Type int required by psycopg2, which cannot handle numpy.int64 - assert id_types == {int} - - -def test_extract_control_units( - reset_test_data, expected_control_unit_ids, expected_control_units -): - units = extract_control_units.run( - control_unit_ids=expected_control_unit_ids - ) - units["email_addresses"] = units.email_addresses.map(sorted) - pd.testing.assert_frame_equal(units, expected_control_units) - - -def test_extract_all_control_units( - reset_test_data, expected_all_control_units -): - units = extract_all_control_units.run() - units["email_addresses"] = units.email_addresses.map(sorted) - - pd.testing.assert_frame_equal(units, expected_all_control_units) - - -def test_to_control_unit_actions(expected_env_actions, expected_control_units): - - period = Period( - start=datetime(1996, 6, 11, 2, 52, 36), - end=datetime(1996, 6, 13, 6, 17, 18), - ) - - control_unit_actions = to_control_unit_actions.run( - env_actions=expected_env_actions, - period=period, - control_units=expected_control_units, - ) - - assert len(control_unit_actions) == 2 - - assert isinstance(control_unit_actions[0], ControlUnitActions) - assert control_unit_actions[0].control_unit.control_unit_id == 10018 - assert control_unit_actions[0].period == period - pd.testing.assert_frame_equal( - control_unit_actions[0].controls, - expected_env_actions.iloc[[1]].reset_index(drop=True), - ) - pd.testing.assert_frame_equal( - control_unit_actions[0].surveillances, - expected_env_actions.iloc[[3]].reset_index(drop=True), - ) - - assert control_unit_actions[1].control_unit.control_unit_id == 10019 - assert control_unit_actions[1].period == period - pd.testing.assert_frame_equal( - control_unit_actions[1].controls, - expected_env_actions.iloc[[2]].reset_index(drop=True), - ) - pd.testing.assert_frame_equal( - control_unit_actions[1].surveillances, - expected_env_actions.iloc[[4]].reset_index(drop=True), - ) - pd.testing.assert_frame_equal( - control_unit_actions[1].late_surveillances, - expected_env_actions.iloc[[0]].reset_index(drop=True), - ) - - -def test_get_template(): - template = get_template.run() - assert isinstance(template, Template) - - -def test_render(sample_control_unit_actions): - template = get_template.run() - html = render.run( - control_unit_actions=sample_control_unit_actions, template=template - ) - - # Uncomment to update the expected html file - # with open(TEST_DATA_LOCATION / "emails/expected_rendered_email.html", "w") as f: - # f.write(html) - - with open( - TEST_DATA_LOCATION / "emails/expected_rendered_email.html", "r" - ) as f: - expected_html = f.read() - - assert html == expected_html - - -def test_render_when_unit_as_no_actions( - sample_control_unit_actions_without_actions, -): - template = get_template.run() - html = render.run( - control_unit_actions=sample_control_unit_actions_without_actions, - template=template, - ) - - # Uncomment to update the expected html file - # with open( - # TEST_DATA_LOCATION / "emails/expected_rendered_email_without_actions.html", "w" - # ) as f: - # f.write(html) - - with open( - TEST_DATA_LOCATION - / "emails/expected_rendered_email_without_actions.html", - "r", - ) as f: - expected_html = f.read() - - assert html == expected_html - - -@pytest.mark.parametrize("test_mode", [False, True]) -def test_create_email(sample_control_unit_actions, expected_email, test_mode): - - email = create_email.run( - html="Bonjour ceci est un email test.", - actions=sample_control_unit_actions, - test_mode=test_mode, - ) - - assert email["Subject"] == expected_email["Subject"] - assert email["From"] == expected_email["From"] - assert ( - email["To"] == CACEM_EMAIL_ADDRESS - if test_mode - else expected_email["To"] - ) - assert email["Reply-To"] == expected_email["Reply-To"] - assert email.get_content_type() == expected_email.get_content_type() - - body = email.get_body() - expected_body = expected_email.get_body() - assert body.get_content_type() == expected_body.get_content_type() - - assert body.get_charsets() == expected_body.get_charsets() - assert body.get_content() == expected_body.get_content() - - -@pytest.mark.parametrize( - "is_integration,send_email_outcome", - [ - (False, SMTPDataError(100, "Erreur SMTP")), - (False, dict()), - (False, {"email2@email.com": (550, "Email cound not be sent.")}), - (True, Exception("Autre erreur")), - ], -) -@patch("src.pipeline.flows.email_actions_to_units.send_email") -@patch("src.pipeline.flows.email_actions_to_units.sleep") -def test_send_env_actions_email( - mock_sleep, - mock_send_email, - expected_email, - sample_control_unit_actions, - is_integration, - send_email_outcome, -): - def send_email_side_effect(message): - if isinstance(send_email_outcome, Exception): - raise send_email_outcome - else: - return send_email_outcome - - mock_send_email.side_effect = send_email_side_effect - - sent_messages = send_env_actions_email.run( - message=expected_email, - actions=sample_control_unit_actions, - is_integration=is_integration, - ) - assert len(sent_messages) == 2 - for msg in sent_messages: - success = True - error_code = None - error_message = None - addressee = msg.email_address - if not is_integration: - if isinstance(send_email_outcome, SMTPDataError): - success = False - error_message = ( - "The server replied with an unexpected error code " - "(other than a refusal of a recipient)." - ) - else: - if msg.email_address in send_email_outcome: - success = False - error_code, error_message = send_email_outcome[addressee] - assert isinstance(msg, ControlUnitActionsSentMessage) - assert ( - msg.control_unit_id - == sample_control_unit_actions.control_unit.control_unit_id - ) - assert ( - msg.control_unit_name - == sample_control_unit_actions.control_unit.control_unit_name - ) - assert msg.email_address == addressee - assert ( - msg.actions_min_datetime_utc - == sample_control_unit_actions.period.start - ) - assert ( - msg.actions_max_datetime_utc - == sample_control_unit_actions.period.end - ) - assert msg.number_of_actions == len( - sample_control_unit_actions.controls - ) + len(sample_control_unit_actions.surveillances) + len( - sample_control_unit_actions.late_controls - ) + len( - sample_control_unit_actions.late_surveillances - ) - assert msg.success == success - assert msg.error_code == error_code - assert msg.error_message == error_message - - -def test_control_unit_actions_list_to_df( - control_unit_actions_sent_messages, control_unit_actions_sent_messages_df -): - - df = control_unit_actions_list_to_df.run( - control_unit_actions_sent_messages - ) - pd.testing.assert_frame_equal(df, control_unit_actions_sent_messages_df) - - -def test_load_emails_sent_to_control_units( - reset_test_data, control_unit_actions_sent_messages_df -): - - query = "SELECT * FROM emails_sent_to_control_units ORDER BY email_address" - - initial_emails = read_query(db="monitorenv_remote", query=query) - - load_emails_sent_to_control_units.run( - control_unit_actions_sent_messages_df - ) - emails_after_one_run = read_query(db="monitorenv_remote", query=query) - - load_emails_sent_to_control_units.run( - control_unit_actions_sent_messages_df - ) - emails_after_two_runs = read_query(db="monitorenv_remote", query=query) - - assert len(initial_emails) == 0 - assert len(emails_after_one_run) == len(emails_after_two_runs) == 2 - pd.testing.assert_frame_equal( - emails_after_one_run.drop(columns=["id"]), - emails_after_two_runs.drop(columns=["id"]), - ) - - -@pytest.mark.parametrize( - "email_all_units,expected_number_of_sent_emails", [(True, 4), (False, 3)] -) -def test_flow( - reset_test_data, email_all_units, expected_number_of_sent_emails -): - now = datetime.utcnow() - d1 = datetime(2022, 10, 1) - d2 = datetime(2022, 12, 6) - start_days_ago = int((now - d1).total_seconds() / 60 / 60 / 24) + 1 - end_days_ago = int((now - d2).total_seconds() / 60 / 60 / 24) + 1 - - query = "SELECT * FROM emails_sent_to_control_units ORDER BY email_address" - initial_emails = read_query(db="monitorenv_remote", query=query) - - flow.schedule = None - state = flow.run( - test_mode=False, - is_integration=True, - start_days_ago=start_days_ago, - end_days_ago=end_days_ago, - email_all_units=email_all_units, - ) - assert state.is_successful() - - final_emails = read_query(db="monitorenv_remote", query=query) - assert len(initial_emails) == 0 - assert len(final_emails) == expected_number_of_sent_emails diff --git a/datascience/tests/test_pipeline/test_flows/test_historic_control_units.py b/datascience/tests/test_pipeline/test_flows/test_historic_control_units.py deleted file mode 100644 index 6f0473fafe..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_historic_control_units.py +++ /dev/null @@ -1,113 +0,0 @@ -from unittest.mock import patch - -import pandas as pd -import pytest -import sqlalchemy - -from src.pipeline.flows.historic_control_units import ( - check_id_range, - extract_historic_control_units, - load_historic_control_units, - transform_control_units, -) -from src.read_query import read_query -from tests.mocks import mock_extract_side_effect - - -@pytest.fixture -def historic_control_units() -> pd.DataFrame: - return pd.DataFrame( - { - "id": [1315, 1, 1401, 62, 1485, 1486, 1487], - "administration_id": [1011, 3, 4, 10, 4, 4, 3], - "name": [ - "Unité 1", - "Police de la pêche de poissons qui nagent", - "Les Barbouzes", - "Unité 2", - "Unité 3", - "Unité 3", - "Unité 3", - ], - } - ) - - -@pytest.fixture -def transformed_control_units() -> pd.DataFrame: - return pd.DataFrame( - { - "id": [1315, 1, 1401, 62, 1485, 1486, 1487], - "administration_id": [1011, 3, 4, 10, 4, 4, 3], - "name": [ - "Unité 1 (historique)", - "Police de la pêche de poissons qui nagent (historique)", - "Les Barbouzes (historique)", - "Unité 2 (historique)", - "Unité 3 (historique)", - "Unité 3 [2] (historique)", - "Unité 3 [3] (historique)", - ], - "archived": [True, True, True, True, True, True, True], - } - ) - - -@patch("src.pipeline.flows.historic_control_units.extract") -def test_extract_historic_control_units(mock_extract): - mock_extract.side_effect = mock_extract_side_effect - query = extract_historic_control_units.run() - assert isinstance(query, sqlalchemy.sql.elements.TextClause) - - -def test_transform_control_units(historic_control_units, transformed_control_units): - control_units = transform_control_units.run(historic_control_units) - pd.testing.assert_frame_equal(control_units, transformed_control_units) - - -def test_check_id_range_returns_input_when_test_passes( - transformed_control_units, -): - control_units = check_id_range.run(transformed_control_units, max_id=10000) - pd.testing.assert_frame_equal(control_units, transformed_control_units) - - -def test_check_id_range_raises_value_error_when_test_fails( - transformed_control_units, -): - with pytest.raises(ValueError): - check_id_range.run(transformed_control_units, max_id=1000) - - -def test_load_control_units(reset_test_data, transformed_control_units): - query = "SELECT * FROM control_units ORDER BY id" - initial_control_units = read_query("monitorenv_remote", query) - - load_historic_control_units.run(transformed_control_units) - control_units = read_query("monitorenv_remote", query) - - initial_control_units_ids = set(initial_control_units.id) - control_units_ids = set(control_units.id) - updated_control_units_ids = set(transformed_control_units.id) - - assert ( - updated_control_units_ids.union(initial_control_units_ids) == control_units_ids - ) - assert ( - initial_control_units.loc[initial_control_units.id == 1315, "name"].values[0] - == "Unité 1 ancien nom" - ) - assert ( - control_units.loc[control_units.id == 1315, "name"].values[0] - == "Unité 1 (historique)" - ) - - # Re-loading the same data should not make any difference - load_historic_control_units.run(transformed_control_units) - control_units_bis = read_query("monitorenv_remote", query) - pd.testing.assert_frame_equal( - # Ignore both `created_at_utc` and `updated_at_utc` since they are "on time" - control_units.drop(columns=["created_at_utc", "updated_at_utc"]), - control_units_bis.drop(columns=["created_at_utc", "updated_at_utc"]), - check_like=True, - ) diff --git a/datascience/tests/test_pipeline/test_flows/test_historic_controls.py b/datascience/tests/test_pipeline/test_flows/test_historic_controls.py deleted file mode 100644 index 06589ffc35..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_historic_controls.py +++ /dev/null @@ -1,631 +0,0 @@ -import uuid -from datetime import datetime -from unittest.mock import MagicMock, patch - -import pandas as pd -import pytest -from prefect import task - -from src.pipeline.flows.historic_controls import ( - flow, - make_env_actions, - make_env_mission_units, - make_env_missions, -) -from src.read_query import read_query - -historic_controls_df = pd.DataFrame( - { - "id": [10009, 10010, 10011, 10012, 10013, 10014, 10015], - "themes": [ - ( - "Police des espèces protégées et de leurs habitats" - "Travaux en milieu marin (dragage, clapage, infrastructures)" - ), - "Pêche à pied", - "Domanialité publique dont circulation", - "Pêche à piedPolice de la chasse en mer", - None, - "Police des aires marines protégées", - "Pêche à pied", - ], - "action_number_of_controls": [5, 1, 2, 70, 16, 42, 16], - "natinf": [ - None, - "10041", - "10080,10010", - "2785", - None, - "2798,10042", - None, - ], - "protected_species": [ - "Flore,Mamifères marins,Oiseaux", - "Oiseaux,Flore", - None, - "Flore", - None, - "Oiseaux,Mammifères marins", - None, - ], - "mission_id": [10012, 10013, 10015, 10017, 10018, 10019, 10042], - "action_start_datetime_utc": [ - datetime(2017, 12, 3, 9, 40), - datetime(2017, 3, 14, 10), - datetime(2017, 3, 23, 21), - datetime(2017, 3, 28, 10), - datetime(2017, 3, 29, 12, 15), - datetime(2017, 3, 29, 11, 45), - datetime(2017, 4, 29, 11, 45), - ], - "action_type": [ - "CONTROL", - "CONTROL", - "CONTROL", - "CONTROL", - "CONTROL", - "CONTROL", - "CONTROL", - ], - } -) -historic_missions_df = pd.DataFrame( - { - "id": [10012, 10013, 10015, 10017, 10018, 10019, 10050], - "mission_type": ["SEA", "SEA", "LAND", "SEA", "LAND", "AIR", "LAND"], - "open_by": ["JBL", "GYT", "RIO", "CBG", "LEG", "RIO", "CRO"], - "observations_cacem": [ - "91 pàp pro contrôlés/ 4 pàp pro en infraction surquotas", - ( - "Surveillance: protection des aires marines / protection des espèces" - " sensibles dont perturbation intentionnelle des espèces protégées RAS" - ), - None, - "contrôle de 3 pêcheurs plaisanciers : RAS", - None, - "pas pris la commune lors de la déclaration", - None, - ], - "facade": [ - "DIRM NAMO", - "DIRM Med", - "DIRM NAMO", - "DIRM NAMO", - "DIRM NAMO", - "DIRM NAMO", - "DIRM Med", - ], - "start_datetime_utc": [ - pd.Timestamp("29/03/2017 11:45:00"), - pd.Timestamp("30/03/2017 07:00:00"), - pd.Timestamp("06/04/2017 09:00:00"), - pd.Timestamp("09/04/2017 10:45:00"), - pd.Timestamp("15/04/2017 10:00:00"), - pd.Timestamp("17/04/2017 08:00:00"), - pd.Timestamp("22/04/2017 07:30:00"), - ], - "end_datetime_utc": [ - pd.Timestamp("29/03/2017 16:00:00"), - pd.Timestamp("30/03/2017 10:30:00"), - pd.Timestamp("06/04/2017 13:15:00"), - pd.Timestamp("09/04/2017 11:10:00"), - pd.Timestamp("15/04/2017 18:00:00"), - pd.Timestamp("17/04/2017 13:30:00"), - pd.Timestamp("22/04/2017 10:30:00"), - ], - "completed_by": ["VSQ", "VSQ", "GYT", "RIO", "CBG", "VSQ", "VSQ"], - "mission_source": [ - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - ], - } -) -historic_missions_units_df = pd.DataFrame( - { - "mission_id": [10012, 10013, 10015, 10017, 10018, 10019, 10050, 10042], - "control_unit_id": [ - 10005, - 10005, - 10005, - 10011, - 10013, - 10009, - 10010, - 10010, - ], - } -) - - -@pytest.fixture -def historic_controls() -> pd.DataFrame: - return historic_controls_df - - -@pytest.fixture -def transformed_historic_controls() -> pd.DataFrame: - - return pd.DataFrame( - { - "id": [ - uuid.UUID("27c395ff-9357-438c-b62b-08861780c33a"), - uuid.UUID("27c395ff-9357-438c-b62b-08861780c33a"), - uuid.UUID("27c395ff-9357-438c-b62b-08861780c33a"), - uuid.UUID("27c395ff-9357-438c-b62b-08861780c33a"), - uuid.UUID("27c395ff-9357-438c-b62b-08861780c33a"), - uuid.UUID("27c395ff-9357-438c-b62b-08861780c33a"), - ], - "mission_id": [ - 10012 - 100000, - 10013 - 100000, - 10015 - 100000, - 10017 - 100000, - 10018 - 100000, - 10019 - 100000, - ], - "action_type": [ - "CONTROL", - "CONTROL", - "CONTROL", - "CONTROL", - "CONTROL", - "CONTROL", - ], - "value": [ - { - "themes": [ - { - "theme": "Police des espèces protégées et de leurs habitats (faune et flore)", - "protected_species": [ - "Flore", - "Mamifères marins", - "Oiseaux", - ], - }, - { - "theme": "Travaux en milieu marin", - "protected_species": [ - "Flore", - "Mamifères marins", - "Oiseaux", - ], - }, - ], - "infractions": [], - "vehicleType": None, - "actionTargetType": None, - "actionNumberOfControls": int(5), - }, - { - "themes": [ - { - "theme": "Pêche à pied", - "protected_species": ["Oiseaux", "Flore"], - } - ], - "infractions": [ - { - "id": uuid.UUID( - "27c395ff-9357-438c-b62b-08861780c33a" - ), - "natinf": ["10041"], - "toProcess": False, - } - ], - "vehicleType": None, - "actionTargetType": None, - "actionNumberOfControls": int(1), - }, - { - "themes": [ - { - "theme": "Domanialité publique dont circulation", - "protected_species": [], - } - ], - "infractions": [ - { - "id": uuid.UUID( - "27c395ff-9357-438c-b62b-08861780c33a" - ), - "natinf": ["10080"], - "toProcess": False, - }, - { - "id": uuid.UUID( - "27c395ff-9357-438c-b62b-08861780c33a" - ), - "natinf": ["10010"], - "toProcess": False, - }, - ], - "vehicleType": None, - "actionTargetType": None, - "actionNumberOfControls": int(2), - }, - { - "themes": [ - { - "theme": "Pêche à pied", - "protected_species": ["Flore"], - }, - { - "theme": "Pêche de loisir", - "protected_species": ["Flore"], - }, - ], - "infractions": [ - { - "id": uuid.UUID( - "27c395ff-9357-438c-b62b-08861780c33a" - ), - "natinf": ["2785"], - "toProcess": False, - } - ], - "vehicleType": None, - "actionTargetType": None, - "actionNumberOfControls": int(70), - }, - { - "themes": [], - "infractions": [], - "vehicleType": None, - "actionTargetType": None, - "actionNumberOfControls": int(16), - }, - { - "themes": [ - { - "theme": "Police des parcs nationaux", - "protected_species": [ - "Oiseaux", - "Mammifères marins", - ], - }, - ], - "infractions": [ - { - "id": uuid.UUID( - "27c395ff-9357-438c-b62b-08861780c33a" - ), - "natinf": ["2798"], - "toProcess": False, - }, - { - "id": uuid.UUID( - "27c395ff-9357-438c-b62b-08861780c33a" - ), - "natinf": ["10042"], - "toProcess": False, - }, - ], - "vehicleType": None, - "actionTargetType": None, - "actionNumberOfControls": int(42), - }, - ], - "action_start_datetime_utc": [ - pd.Timestamp("2017-12-03 09:40:00"), - pd.Timestamp("2017-03-14 10:00:00"), - pd.Timestamp("2017-03-23 21:00:00"), - pd.Timestamp("2017-03-28 10:00:00"), - pd.Timestamp("2017-03-29 12:15:00"), - pd.Timestamp("2017-03-29 11:45:00"), - ], - } - ) - - -@pytest.fixture -def transformed_historic_controls_non_constant_uuid() -> pd.DataFrame: - temp = transformed_historic_controls - temp["id"] = temp.apply(lambda x: uuid.uuid4(), axis=1) - return temp - - -@pytest.fixture -def historic_missions() -> pd.DataFrame: - return historic_missions_df - - -@pytest.fixture -def historic_controls_without_missing_missions( - historic_missions, -) -> pd.DataFrame: - return historic_controls_df[ - historic_controls_df.mission_id.isin(historic_missions.id.values) - ] - - -@pytest.fixture -def transformed_historic_missions() -> pd.DataFrame: - return pd.DataFrame( - { - "id": [ - 10012 - 100000, - 10013 - 100000, - 10015 - 100000, - 10017 - 100000, - 10018 - 100000, - 10019 - 100000, - 10050 - 100000, - ], - "mission_types": [ - ["SEA"], - ["SEA"], - ["LAND"], - ["SEA"], - ["LAND"], - ["AIR"], - ["LAND"], - ], - "open_by": ["JBL", "GYT", "RIO", "CBG", "LEG", "RIO", "CRO"], - "observations_cacem": [ - "91 pàp pro contrôlés/ 4 pàp pro en infraction surquotas", - ( - "Surveillance: protection des aires marines / protection des " - "espèces sensibles dont perturbation intentionnelle des espèces " - "protégées RAS" - ), - None, - "contrôle de 3 pêcheurs plaisanciers : RAS", - None, - "pas pris la commune lors de la déclaration", - None, - ], - "facade": ["NAMO", "MED", "NAMO", "NAMO", "NAMO", "NAMO", "MED"], - "start_datetime_utc": [ - pd.Timestamp("29/03/2017 11:45:00"), - pd.Timestamp("30/03/2017 07:00:00"), - pd.Timestamp("06/04/2017 09:00:00"), - pd.Timestamp("09/04/2017 10:45:00"), - pd.Timestamp("15/04/2017 10:00:00"), - pd.Timestamp("17/04/2017 08:00:00"), - pd.Timestamp("22/04/2017 07:30:00"), - ], - "end_datetime_utc": [ - pd.Timestamp("29/03/2017 16:00:00"), - pd.Timestamp("30/03/2017 10:30:00"), - pd.Timestamp("06/04/2017 13:15:00"), - pd.Timestamp("09/04/2017 11:10:00"), - pd.Timestamp("15/04/2017 18:00:00"), - pd.Timestamp("17/04/2017 13:30:00"), - pd.Timestamp("22/04/2017 10:30:00"), - ], - "completed_by": ["VSQ", "VSQ", "GYT", "RIO", "CBG", "VSQ", "VSQ"], - "mission_source": [ - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - "POSEIDON_CACEM", - ], - "deleted": [False, False, False, False, False, False, False], - } - ) - - -@pytest.fixture -def historic_missions_units() -> pd.DataFrame: - return historic_missions_units_df - - -@pytest.fixture -def historic_missions_units_without_missing_missions( - historic_missions, -) -> pd.DataFrame: - return historic_missions_units_df[ - historic_missions_units_df.mission_id.isin(historic_missions.id.values) - ] - - -@pytest.fixture -def transformed_missions_units() -> pd.DataFrame: - return pd.DataFrame( - { - "mission_id": [ - 10012 - 100000, - 10013 - 100000, - 10015 - 100000, - 10017 - 100000, - 10018 - 100000, - 10019 - 100000, - 10050 - 100000, - ], - "control_unit_id": [ - 10005, - 10005, - 10005, - 10011, - 10013, - 10009, - 10010, - ], - } - ) - - -def is_valid_uuid(uuid_to_test): - assert isinstance(uuid_to_test, uuid.UUID) - - -def mock_uuid4() -> uuid.UUID: - return uuid.UUID("27c395ff-9357-438c-b62b-08861780c33a") - - -mock_uuid = MagicMock() -mock_uuid.uuid4 = mock_uuid4 - - -@task(checkpoint=False) -def mock_extract_historic_controls_in_flow() -> pd.DataFrame: - return historic_controls_df - - -@task(checkpoint=False) -def mock_extract_historic_missions_in_flow() -> pd.DataFrame: - return historic_missions_df - - -@task(checkpoint=False) -def mock_extract_historic_missions_units_in_flow() -> pd.DataFrame: - return historic_missions_units_df - - -flow.replace( - flow.get_tasks("extract_historic_controls")[0], - mock_extract_historic_controls_in_flow, -) -flow.replace( - flow.get_tasks("extract_historic_missions")[0], - mock_extract_historic_missions_in_flow, -) -flow.replace( - flow.get_tasks("extract_historic_missions_units")[0], - mock_extract_historic_missions_units_in_flow, -) - - -def test_make_env_actions( - historic_controls_without_missing_missions, transformed_historic_controls -): - with patch("src.pipeline.flows.historic_controls.uuid", mock_uuid): - res = make_env_actions.run(historic_controls_without_missing_missions) - pd.testing.assert_frame_equal(res, transformed_historic_controls) - - -def test_env_missions(historic_missions, transformed_historic_missions): - historic_missions = make_env_missions.run(historic_missions) - pd.testing.assert_frame_equal( - historic_missions, transformed_historic_missions, check_like=True - ) - - -def test_env_missions_units( - historic_missions_units_without_missing_missions, - transformed_missions_units, -): - historic_missions_units = make_env_mission_units.run( - historic_missions_units_without_missing_missions - ) - pd.testing.assert_frame_equal( - historic_missions_units, transformed_missions_units - ) - - -def test_flow( - reset_test_data, - transformed_historic_controls, - transformed_historic_missions, - transformed_missions_units, -): - - query_controls = ( - "SELECT id, mission_id, action_type, value, action_start_datetime_utc," - " geom FROM env_actions ORDER BY id" - ) - initial_controls = read_query("monitorenv_remote", query_controls) - - query_missions = ( - "SELECT id, mission_types, open_by, observations_cacem, facade," - " start_datetime_utc, completed_by, mission_source," - " observations_cnsp, deleted, geom FROM missions ORDER BY id" - ) - initial_missions = read_query("monitorenv_remote", query_missions) - - query_missions_units = ( - "SELECT id, mission_id, control_unit_id " - "FROM missions_control_units ORDER BY id" - ) - - query_missions_units_with_mission_source = ( - "SELECT missions_control_units.id, mission_id," - " control_unit_id, missions.mission_source " - "FROM missions_control_units LEFT JOIN missions " - "ON missions.id=missions_control_units.mission_id ORDER BY id" - ) - initial_missions_units_with_mission_source = read_query( - "monitorenv_remote", query_missions_units_with_mission_source - ) - - state = flow.run() - assert state.is_successful() - - controls = read_query("monitorenv_remote", query_controls) - missions = read_query("monitorenv_remote", query_missions) - missions_units = read_query("monitorenv_remote", query_missions_units) - - initial_missions_ids_poseidon = set( - initial_missions.id[ - initial_missions.mission_source == "POSEIDON_CACEM" - ] - ) - initial_missions_ids_no_poseidon = set( - initial_missions.id[ - initial_missions.mission_source != "POSEIDON_CACEM" - ] - ) - - # test datas to load controls - initial_controls_time = set(initial_controls.action_start_datetime_utc) - test_controls_time = set( - transformed_historic_controls.action_start_datetime_utc - ) - updated_controls_time = set(controls.action_start_datetime_utc) - - # test datas to load missions - test_missions_ids = set(transformed_historic_missions.id) - updated_missions_ids = set(missions.id) - - # test datas to load missions_units - initial_missions_units_with_mission_source_ids_poseidon = set( - initial_missions_units_with_mission_source.mission_id[ - initial_missions_units_with_mission_source.mission_source - == "POSEIDON_CACEM" - ] - ) - initial_missions_units_with_mission_source_ids_no_poseidon = set( - initial_missions_units_with_mission_source.mission_id[ - initial_missions_units_with_mission_source.mission_source - != "POSEIDON_CACEM" - ] - ) - - updated_control_unit_missions_ids = set(missions_units.mission_id) - test_control_unit_missions_ids = set(transformed_missions_units.mission_id) - - assert ( - test_controls_time.union(initial_controls_time) - == updated_controls_time - ) - - print( - "Ids that should not be in the updated version " - f"of the missions table : {initial_missions_ids_poseidon}" - ) - - assert -95690 and -95689 not in updated_missions_ids - assert initial_missions_ids_no_poseidon.issubset(updated_missions_ids) - assert test_missions_ids.issubset(updated_missions_ids) - - print( - "Ids that should not be in the updated version of" - " the control_unit table : " - f"{initial_missions_units_with_mission_source_ids_poseidon}" - ) - - assert -95690 and -95689 not in updated_control_unit_missions_ids - assert initial_missions_units_with_mission_source_ids_no_poseidon.issubset( - updated_control_unit_missions_ids - ) - assert test_control_unit_missions_ids.issubset( - updated_control_unit_missions_ids - ) diff --git a/datascience/tests/test_pipeline/test_flows/test_infractions.py b/datascience/tests/test_pipeline/test_flows/test_infractions.py deleted file mode 100644 index c2c4846eaa..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_infractions.py +++ /dev/null @@ -1,82 +0,0 @@ -from unittest.mock import patch - -import pandas as pd -import pytest -import sqlalchemy - -from src.pipeline.flows.infractions import ( - clean_infractions, - extract_infractions, - load_infractions, -) -from src.read_query import read_query -from tests.mocks import mock_extract_side_effect - - -@patch("src.pipeline.flows.infractions.extract") -def test_extract_infractions(mock_extract): - mock_extract.side_effect = mock_extract_side_effect - query = extract_infractions.run() - assert isinstance(query, sqlalchemy.sql.elements.TextClause) - - -@pytest.fixture -def infractions() -> pd.DataFrame: - return pd.DataFrame( - { - "natinf_code": [20978, 22564, 30771, 30788, 40409], - "regulation": [None, None, None, None, "d89-273"], - "infraction_category": [ - "Environnement", - "Environnement", - "Environnement", - "Environnement", - "Pêche", - ], - "infraction": [ - "DETENTION D'ESPECE ANIMALE NON DOMESTIQUE - ESPECE PROTEGEE", - "USAGE DE FOYER LUMINEUX POUR LA PECHE SOUS-MARINE DE LOISIR", - "USAGE ILLEGAL SCOOTER SOUS-MARIN", - "EXERCICE DE LA PECHE SOUS-MARINE DE LOISIR INTERDITE", - "DECLARATION ERRONEE", - ], - } - ) - - -@pytest.fixture -def cleaned_infractions() -> pd.DataFrame: - return pd.DataFrame( - { - "natinf_code": [20978, 22564, 30771, 30788, 40409], - "regulation": [None, None, None, None, "d89-273"], - "infraction_category": [ - "Environnement", - "Environnement", - "Environnement", - "Environnement", - "Pêche", - ], - "infraction": [ - "Detention d'espece animale non domestique - espece protegee", - "Usage de foyer lumineux pour la peche sous-marine de loisir", - "Usage illegal scooter sous-marin", - "Exercice de la peche sous-marine de loisir interdite", - "Declaration erronee", - ], - } - ) - - -def test_clean_infractions(infractions, cleaned_infractions): - res = clean_infractions.run(infractions) - pd.testing.assert_frame_equal(res, cleaned_infractions) - - -def test_load_infractions(reset_test_data, cleaned_infractions): - load_infractions.run(cleaned_infractions) - loaded_infractions = read_query( - "monitorenv_remote", "SELECT * FROM natinfs ORDER BY natinf_code" - ) - - pd.testing.assert_frame_equal(loaded_infractions, cleaned_infractions) diff --git a/datascience/tests/test_pipeline/test_flows/test_insert_historical_controls_data.py b/datascience/tests/test_pipeline/test_flows/test_insert_historical_controls_data.py deleted file mode 100644 index ced6bf8aec..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_insert_historical_controls_data.py +++ /dev/null @@ -1,6 +0,0 @@ -from src.read_query import read_query - - -def test_dummy(reset_test_data): - eez_areas = read_query("monitorenv_remote", "SELECT * FROM eez_areas") - print(eez_areas) diff --git a/datascience/tests/test_pipeline/test_flows/test_localized_areas.py b/datascience/tests/test_pipeline/test_flows/test_localized_areas.py deleted file mode 100644 index bd6c2099bd..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_localized_areas.py +++ /dev/null @@ -1,19 +0,0 @@ -from geopandas.testing import assert_geodataframe_equal - -from src.pipeline.flows.localized_areas import extract_localized_areas, load_localized_areas -from src.read_query import read_query - -def test_load_localized_areas(create_cacem_tables): - localized_areas = extract_localized_areas.run() - assert localized_areas.shape[0] == 1 - - load_localized_areas.run(localized_areas) - imported_localized_areas = read_query( - db="monitorenv_remote", - query="SELECT id, amp_ids, control_unit_ids, group_name, geom, name FROM localized_areas", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - - assert_geodataframe_equal(localized_areas, imported_localized_areas) \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/test_marpol.py b/datascience/tests/test_pipeline/test_flows/test_marpol.py deleted file mode 100644 index 03e6b4001d..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_marpol.py +++ /dev/null @@ -1,21 +0,0 @@ - -from geopandas.testing import assert_geodataframe_equal - -from src.pipeline.flows.marpol import extract_marpol, load_marpol -from src.read_query import read_query - -def test_load_marpol(create_cacem_tables): - marpol = extract_marpol.run() - assert marpol.shape[0] == 1 - - load_marpol.run(marpol) - imported_marpol = read_query( - db="monitorenv_remote", - query="SELECT * FROM marpol", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - - assert_geodataframe_equal(marpol, imported_marpol) - diff --git a/datascience/tests/test_pipeline/test_flows/test_refresh_materialized_views.py b/datascience/tests/test_pipeline/test_flows/test_refresh_materialized_views.py deleted file mode 100644 index 8dff3ddbcd..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_refresh_materialized_views.py +++ /dev/null @@ -1,130 +0,0 @@ -import pandas as pd -from sqlalchemy import text - -from src.db_config import create_engine -from src.pipeline.flows.refresh_materialized_view import flow -from src.read_query import read_query -from tests.mocks import mock_check_flow_not_running - -flow.replace( - flow.get_tasks("check_flow_not_running")[0], mock_check_flow_not_running -) - - -def test_refresh_analytics_actions(reset_test_data): - - e = create_engine("monitorenv_remote") - query = text( - """ - SELECT * - FROM analytics_actions - ORDER BY id, control_unit, theme_level_1, theme_level_2 - """ - ) - - initial_actions = read_query("monitorenv_remote", query) - - with e.begin() as connection: - connection.execute( - text( - ( - "WITH env_actions_to_delete AS (" - " SELECT DISTINCT id " - "FROM env_actions " - "WHERE mission_id = 12" - "), " - "deleted_action_themes AS (" - " DELETE FROM themes_env_actions " - " WHERE env_actions_id IN (SELECT id FROM env_actions_to_delete)" - ") " - "DELETE FROM env_actions WHERE id IN (" - " SELECT id FROM env_actions_to_delete" - ")" - ) - ) - ) - - actions_before_refresh = read_query("monitorenv_remote", query) - - flow.schedule = None - state = flow.run(view_name="analytics_actions", schema="public") - - assert state.is_successful() - - actions_after_refresh = read_query("monitorenv_remote", query) - - assert len(initial_actions) == 9 - assert len(actions_before_refresh) == 9 - assert len(actions_after_refresh) == 3 - - pd.testing.assert_frame_equal(initial_actions, actions_before_refresh) - pd.testing.assert_frame_equal( - initial_actions.query("mission_id != 12").reset_index(drop=True), - actions_after_refresh, - check_dtype=False, - ) - - -def test_refresh_analytics_surveillance_density_map(reset_test_data): - - e = create_engine("monitorenv_remote") - query = text( - """ - SELECT * - FROM analytics_surveillance_density_map - ORDER BY action_id, latitude, longitude - """ - ) - - initial_surveillance_density_map = read_query("monitorenv_remote", query) - - with e.begin() as connection: - connection.execute( - text( - ( - "WITH env_actions_to_delete AS (" - " SELECT DISTINCT id " - "FROM env_actions " - "WHERE mission_id = 12" - "), " - "deleted_action_themes AS (" - " DELETE FROM themes_env_actions " - " WHERE env_actions_id IN (SELECT id FROM env_actions_to_delete)" - ") " - "DELETE FROM env_actions WHERE id IN (" - " SELECT id FROM env_actions_to_delete" - ")" - ) - ) - ) - - surveillance_density_map_before_refresh = read_query( - "monitorenv_remote", query - ) - - flow.schedule = None - state = flow.run( - view_name="analytics_surveillance_density_map", schema="public" - ) - - assert state.is_successful() - - surveillance_density_map_after_refresh = read_query( - "monitorenv_remote", query - ) - - assert len(initial_surveillance_density_map) == 123 - assert len(surveillance_density_map_before_refresh) == 123 - assert len(surveillance_density_map_after_refresh) == 121 - - pd.testing.assert_frame_equal( - initial_surveillance_density_map, - surveillance_density_map_before_refresh, - ) - pd.testing.assert_frame_equal( - initial_surveillance_density_map.query("mission_id != 12").reset_index( - drop=True - ), - surveillance_density_map_after_refresh, - check_dtype=False, - ) diff --git a/datascience/tests/test_pipeline/test_flows/test_regulations.py b/datascience/tests/test_pipeline/test_flows/test_regulations.py deleted file mode 100644 index 8c9cf64406..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_regulations.py +++ /dev/null @@ -1,215 +0,0 @@ -import pandas as pd -import prefect -import pytest - - -from src.pipeline.flows.regulations import load_new_regulations, update_regulatory_areas,load_themes_regulatory_areas, load_tags_regulatory_areas, flow -from src.pipeline.flows.themes_and_tags import load_new_tags, load_new_themes -from src.pipeline.generic_tasks import load -from src.read_query import read_query - -from tests.test_pipeline.test_flows.test_themes_and_tags import generate_tags_data, generate_themes_data - -""" Thèmes """ -@pytest.fixture -def new_themes() -> pd.DataFrame: - return generate_themes_data( - ids=[991, 992, 993, 994], - names=["Thème 1_new", "Thème 2", "Thème 3", "Thème 4"], - parents_id=[991, 991, 992, 993], - started_at=["2025-10-15 00:00:00", "2025-10-16 00:00:00", "2025-10-19 00:00:00", "2025-10-18 00:00:00"], - ended_at=["2026-10-15 23:59:59", "2026-10-16 23:59:59", "2026-10-17 23:59:59", "2026-10-28 23:59:59"], - control_plan_themes_id= [3, 4, 5, 6], - control_plan_sub_themes_id=[7, 8, 9, 10], - control_plan_tags_id=[11, 12, 13, 14], - reportings_control_plan_sub_themes_id=[15, 16, 17, 18], - ) - -@pytest.fixture -def new_tags() -> pd.DataFrame: - return generate_tags_data( - ids=[1, 2, 3, 4], - names=["Tag 1", "Tag 2_new", "Tag 3", "Tag 4"], - parents_id=[1, 1, 2, 2], - started_at=["2025-10-15 00:00:00", "2025-10-16 00:00:00", "2025-10-19 00:00:00", "2025-10-18 00:00:00"], - ended_at=["2026-10-15 23:59:59", "2026-10-16 23:59:59", "2026-10-17 23:59:59", "2026-10-28 23:59:59"], - ) - -def generate_regulatory_area_data(ids, geom, entity_names, layer_names, facades, ref_regs, urls, row_hashes, editions, editeurs, sources, observations, thematiques, dates, duree_validites, temporalites, types): - return pd.DataFrame({ - "id": ids, - "geom": geom, - "entity_name": entity_names, - "layer_name": layer_names, - "facade": facades, - "ref_reg": ref_regs, - "url": urls, - "row_hash": row_hashes, - "edition": editions, - "editeur": editeurs, - "source": sources, - "observation": observations, - "thematique": thematiques, - "date": dates, - "duree_validite": duree_validites, - "temporalite": temporalites, - "type": types - }) - -def generate_themes_regulatory_area_data(themes_id, regulatory_areas_id): - return pd.DataFrame({ - "themes_id": themes_id, - "regulatory_areas_id": regulatory_areas_id, - }) - -def generate_tags_regulatory_area_data(tags_id, regulatory_areas_id): - return pd.DataFrame({ - "tags_id": tags_id, - "regulatory_areas_id": regulatory_areas_id, - }) - -@pytest.fixture -def new_regulatory_areas() -> pd.DataFrame: - return generate_regulatory_area_data( - ids=[3, 4, 5, 6], - geom=[ - "0106000020E610000001000000010300000001000000040000001EA36CE84A6F04C028FCC" - "F619D7F47407B5A4C4F4F6904C06878344D997F4740906370C20E6A04C050111641647F47" - "401EA36CE84A6F04C028FCCF619D7F4740", - "0106000020E61000000100000001030000000100000004000000508B8D44B1B304C014238" - "1B3F47F4740A374D56D789004C0C0F2BF049B7F474033F02B2558B104C0CCA0D40BEE7E47" - "40508B8D44B1B304C0142381B3F47F4740", - "0106000020E61000000100000001030000000100000004000000D2204A8709EBE33F541AC" - "4E69B024940B8BC1FBE94F2E33F387D124AAF02494021642107D81FE43F387D124AAF0249" - "40D2204A8709EBE33F541AC4E69B024940", - "0106000020E61000000100000001030000000100000004000000F57994631533F2BFE2B98" - "CD5455446407A715E737969F3BFEAD7CEDEB655464036ED5A29A137F4BF97F69352CC3446" - "40F57994631533F2BFE2B98CD545544640", - ], - entity_names=["Zone 1_new", "Zone 2", "Zone 3", "Zone 4"], - layer_names=["Layer 1", "Layer 2_new", "Layer 3", "Layer 4"], - facades=["NAMO", "NAMO", "MED_new", "MED"], - ref_regs=["arrêté 1", "arrêté 2", "arrêté 3", "arrêté 4_new"], - urls=["https://dummy_url_1", "https://dummy_url_2", "https://dummy_url_3", "https://dummy_url_4"], - row_hashes=["cacem_row_hash_1", "cacem_row_hash_2", "cacem_row_hash_3", "cacem_row_hash_4"], - editions=["2021-10-14", "2021-10-14", "2021-10-14", "2021-10-14"], - editeurs=["Claire Dagan", "Maxime Perrault", "Vincent Chery", "Mike Data"], - sources=["Source 1", "Source 2", "Source 3", "Source 4"], - observations=["Obs1", "Obs2", "Obs3", "Obs4"], - thematiques=["Mouillage", "Dragage", "Mouillage", "Extraction"], - dates=["2021-09-04", "2021-09-04", "2021-09-04", "2021-09-04"], - duree_validites=["permanent", "permanent", "permanent", None], - temporalites=["permanent", "permanent", "permanent", "temporaire"], - types=["Arrêté préfectoral", "Décret", "Arrêté inter-préfectoral", None] - ) - -@pytest.fixture -def regulatory_areas_to_update() -> pd.DataFrame: - return generate_regulatory_area_data( - ids=[1, 2], - geom=[ - None, - None, - ], - entity_names=["Zone 5", "Zone 6",], - layer_names=["Layer 5", "Layer 6",], - facades=["IDF", "IDF"], - ref_regs=["arrêté 5", "arrêté 6"], - urls=["https://dummy_url_5", "https://dummy_url_6"], - row_hashes=["cacem_row_hash_5", "cacem_row_hash_6"], - editions=["2021-10-15", "2021-10-16",], - editeurs=["Claire Dagan", "Maxime Perrault"], - sources=["Source 5", "Source 6"], - observations=["Obs5", "Obs6"], - thematiques=["Mouillage", "Dragage"], - dates=["2021-09-05", "2021-09-06"], - duree_validites=["permanent", "permanent"], - temporalites=["permanent", "permanent"], - types=["Arrêté préfectoral", "Décret"] - ) - -@pytest.fixture -def themes_regulatory_areas() -> pd.DataFrame: - return generate_themes_regulatory_area_data( - themes_id=[991, 991, 992, 992], - regulatory_areas_id=[1, 2, 3, 4] - ) - -@pytest.fixture -def tags_regulatory_areas() -> pd.DataFrame: - return generate_tags_regulatory_area_data( - tags_id=[1, 1, 2, 2], - regulatory_areas_id=[1, 2, 3, 4] - ) - -def test_load_new_regulatory_areas(reset_test_data, new_regulatory_areas): - old_regulations = read_query( - "monitorenv_remote", - """SELECT - id, geom, entity_name, layer_name, facade, - ref_reg, url, row_hash, edition, editeur, - source, observation, thematique, date, - duree_validite, temporalite, type - FROM public.regulations_cacem - ORDER BY id""" - ) - expectedRegulations = pd.concat([old_regulations, new_regulatory_areas], ignore_index=True) - load_new_regulations.run(new_regulatory_areas) - loaded_regulations = read_query( - "monitorenv_remote", - """SELECT - id, geom, entity_name, layer_name, facade, - ref_reg, url, row_hash, edition, editeur, - source, observation, thematique, date, - duree_validite, temporalite, type - FROM public.regulations_cacem - ORDER BY id""" - ) - pd.testing.assert_frame_equal(loaded_regulations, expectedRegulations) - - -def test_update_new_regulations(reset_test_data, regulatory_areas_to_update): - update_regulatory_areas.run(regulatory_areas_to_update) - updated_regulations = read_query( - "monitorenv_remote", - """SELECT - id, geom, entity_name, layer_name, facade, - ref_reg, url, row_hash, edition, editeur, - source, observation, thematique, date, - duree_validite, temporalite, type - FROM public.regulations_cacem - ORDER BY id""" - ) - pd.testing.assert_frame_equal(updated_regulations, regulatory_areas_to_update) - - -def test_load_themes_regulatory_areas(reset_test_data, create_cacem_tables, new_regulatory_areas: pd.DataFrame, new_themes: pd.DataFrame,themes_regulatory_areas: pd.DataFrame): - load_new_regulations.run(new_regulatory_areas) - load_new_themes.run(new_themes) - load_themes_regulatory_areas.run(themes_regulatory_areas) - imported_themes_regulatory_areas = read_query( - "monitorenv_remote", - """SELECT themes_id, regulatory_areas_id - FROM themes_regulatory_areas - ORDER BY themes_id""" - ) - - pd.testing.assert_frame_equal(themes_regulatory_areas, imported_themes_regulatory_areas) - - -def test_load_tags_regulatory_areas(reset_test_data, create_cacem_tables, new_regulatory_areas: pd.DataFrame, new_tags: pd.DataFrame, tags_regulatory_areas: pd.DataFrame): - load_new_regulations.run(new_regulatory_areas) - load_new_tags.run(new_tags) - load_tags_regulatory_areas.run(tags_regulatory_areas) - imported_tags_regulatory_areas = read_query( - "monitorenv_remote", - """SELECT tags_id, regulatory_areas_id - FROM tags_regulatory_areas - ORDER BY tags_id""" - ) - - pd.testing.assert_frame_equal(tags_regulatory_areas, imported_tags_regulatory_areas) - -def test_flow_regulations(create_cacem_tables, reset_test_data): - state = flow.run() - assert state.is_successful() \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/test_regulations_open_data.py b/datascience/tests/test_pipeline/test_flows/test_regulations_open_data.py deleted file mode 100644 index 0750ae0625..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_regulations_open_data.py +++ /dev/null @@ -1,318 +0,0 @@ -from io import BytesIO - -import geopandas as gpd -from numpy import datetime64 -import pandas as pd -import pytest - -from src.pipeline.flows.regulatory_areas_open_data import ( - extract_regulations_open_data, - flow, - get_regulations_for_csv, - get_regulations_for_geopackage, -) -from tests.mocks import mock_check_flow_not_running, mock_update_resource -from tests.test_pipeline.test_shared_tasks.test_datagouv import make_square_multipolygon - -flow.replace(flow.get_tasks("check_flow_not_running")[0], mock_check_flow_not_running) - -@pytest.fixture -def regulations_open_data(): - return gpd.GeoDataFrame( - { - "id": [ - 1, - 2, - ], - "ent_name": [ - "entity_name1", - "entity_name2", - ], - "url": [ - "url1", - "url2" - ], - "layer_name": [ - "layer_name1", - "layer_name2", - ], - "facade": [ - "MED", - "NAMO", - ], - "ref_reg": [ - "ref_reg1", - "ref_reg2", - ], - "edition": [ - pd.to_datetime("2025-01-01"), - pd.to_datetime("2025-01-01"), - ], - "editeur": [ - "editrice1", - "editeur2", - ], - "source": [ - "source1", - "source2", - ], - "thematique": [ - "thematique1", - "thematique2" - ], - "obs": [ - "observation1", - "observation2", - ], - "date": [ - pd.to_datetime("2010-06-01"), - pd.to_datetime("2005-07-01"), - ], - "date_fin": [ - pd.to_datetime("2024-01-01"), - pd.to_datetime("2025-01-01"), - ], - "validite": [ - "10 ans", - "20 ans", - ], - "tempo": [ - "temporaire", - "permanent", - ], - "type": [ - "Décret", - "Arrêté préfectoral", - ], - "wkt": [ - "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)))", - "MULTIPOLYGON(((120 -20,135 -20,135 -10,120 -10,120 -20)))", - ], - "resume": [ - "resume1", - "resume2", - ], - "poly_name": [ - "polyname1", - "polyname2", - ], - "plan": [ - "plan1", - "plan2", - ], - "geometry": [ - make_square_multipolygon(0, 0, 10, 10), - make_square_multipolygon(120, -20, 15, 10), - ], - }, - ) - -@pytest.fixture -def regulations_for_csv(): - return pd.DataFrame( - { - "id": [ - 1, - 2, - ], - "ent_name": [ - "entity_name1", - "entity_name2", - ], - "url": [ - "url1", - "url2" - ], - "layer_name": [ - "layer_name1", - "layer_name2", - ], - "facade": [ - "MED", - "NAMO", - ], - "ref_reg": [ - "ref_reg1", - "ref_reg2", - ], - "edition": [ - pd.to_datetime("2025-01-01"), - pd.to_datetime("2025-01-01"), - ], - "source": [ - "source1", - "source2", - ], - "obs": [ - "observation1", - "observation2", - ], - "date": [ - pd.to_datetime("2010-06-01"), - pd.to_datetime("2005-07-01"), - ], - "date_fin": [ - pd.to_datetime("2024-01-01"), - pd.to_datetime("2025-01-01"), - ], - "validite": [ - "10 ans", - "20 ans", - ], - "tempo": [ - "temporaire", - "permanent", - ], - "type": [ - "Décret", - "Arrêté préfectoral", - ], - "wkt": [ - "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)))", - "MULTIPOLYGON(((120 -20,135 -20,135 -10,120 -10,120 -20)))", - ], - "resume": [ - "resume1", - "resume2", - ], - "poly_name": [ - "polyname1", - "polyname2", - ], - "plan": [ - "plan1", - "plan2", - ] - }, - ) - -@pytest.fixture -def regulations_for_geopackage(): - return gpd.GeoDataFrame( - { - "id": [ - 1, - 2, - ], - "ent_name": [ - "entity_name1", - "entity_name2", - ], - "url": [ - "url1", - "url2" - ], - "layer_name": [ - "layer_name1", - "layer_name2", - ], - "facade": [ - "MED", - "NAMO", - ], - "ref_reg": [ - "ref_reg1", - "ref_reg2", - ], - "edition": [ - pd.to_datetime("2025-01-01"), - pd.to_datetime("2025-01-01"), - ], - "source": [ - "source1", - "source2", - ], - "obs": [ - "observation1", - "observation2", - ], - "date": [ - pd.to_datetime("2010-06-01"), - pd.to_datetime("2005-07-01"), - ], - "date_fin": [ - pd.to_datetime("2024-01-01"), - pd.to_datetime("2025-01-01"), - ], - "validite": [ - "10 ans", - "20 ans", - ], - "tempo": [ - "temporaire", - "permanent", - ], - "type": [ - "Décret", - "Arrêté préfectoral", - ], - "resume": [ - "resume1", - "resume2", - ], - "poly_name": [ - "polyname1", - "polyname2", - ], - "plan": [ - "plan1", - "plan2", - ], - "geometry": [ - make_square_multipolygon(0, 0, 10, 10), - make_square_multipolygon(120, -20, 15, 10), - ] - }, - ) - -def test_extract_regulations_open_data(create_cacem_tables, reset_test_data, regulations_open_data): - regulations = extract_regulations_open_data.run() - pd.testing.assert_frame_equal(regulations, regulations_open_data) - - -def test_get_regulations_for_csv(regulations_open_data, regulations_for_csv): - regulations = get_regulations_for_csv.run(regulations_open_data) - pd.testing.assert_frame_equal(regulations, regulations_for_csv) - - -def test_get_regulations_for_geopackage( - regulations_open_data, regulations_for_geopackage -): - regulations = get_regulations_for_geopackage.run(regulations_open_data) - pd.testing.assert_frame_equal(regulations, regulations_for_geopackage) - - -def test_flow(create_cacem_tables, reset_test_data, regulations_for_csv, regulations_for_geopackage): - while flow.get_tasks("update_resource"): - flow.replace(flow.get_tasks("update_resource")[0], mock_update_resource) - - flow.schedule = None - state = flow.run() - assert state.is_successful() - - # Check csv file object - csv_file_object = state.result[flow.get_tasks("get_csv_file_object")[0]].result - assert isinstance(csv_file_object, BytesIO) - - df_from_csv_file_object = pd.read_csv(csv_file_object, parse_dates=["date", "date_fin", "edition"]) - pd.testing.assert_frame_equal( - df_from_csv_file_object.convert_dtypes(), regulations_for_csv.convert_dtypes() - ) - - # Check geopackage file object - geopackage_file_object = state.result[ - flow.get_tasks("get_geopackage_file_object")[0] - ].result - assert isinstance(geopackage_file_object, BytesIO) - - layers = ["MED", "NAMO"] - gdfs = [] - for layer in layers: - geopackage_file_object.seek(0) - gdfs.append(gpd.read_file(geopackage_file_object, driver="GPKG", layer=layer)) - - gdf_from_geopackage_file_object = pd.concat(gdfs).reset_index(drop=True) - - pd.testing.assert_frame_equal( - gdf_from_geopackage_file_object, regulations_for_geopackage - ) diff --git a/datascience/tests/test_pipeline/test_flows/test_themes_and_tags.py b/datascience/tests/test_pipeline/test_flows/test_themes_and_tags.py deleted file mode 100644 index bc6511d3f4..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_themes_and_tags.py +++ /dev/null @@ -1,161 +0,0 @@ -import pandas as pd -import prefect -import pytest - - -from src.pipeline.flows.themes_and_tags import load_new_themes, update_themes, load_new_tags, update_tags, flow -from src.pipeline.generic_tasks import load -from src.read_query import read_query - -import pandas as pd -import pytest - -""" Thèmes """ -def generate_themes_data(ids, names, parents_id, started_at, ended_at, control_plan_themes_id, control_plan_sub_themes_id, control_plan_tags_id, reportings_control_plan_sub_themes_id): - return pd.DataFrame({ - "id": ids, - "name": names, - "parent_id": parents_id, - "started_at": pd.to_datetime(started_at), - "ended_at": pd.to_datetime(ended_at), - "control_plan_themes_id": control_plan_themes_id, - "control_plan_sub_themes_id": control_plan_sub_themes_id, - "control_plan_tags_id": control_plan_tags_id, - "reportings_control_plan_sub_themes_id": reportings_control_plan_sub_themes_id - }) - -@pytest.fixture -def new_themes() -> pd.DataFrame: - return generate_themes_data( - ids=[991, 992, 993, 994], - names=["Thème 1_new", "Thème 2", "Thème 3", "Thème 4"], - parents_id=[991, 991, 992, 993], - started_at=["2025-10-15 00:00:00", "2025-10-16 00:00:00", "2025-10-19 00:00:00", "2025-10-18 00:00:00"], - ended_at=["2026-10-15 23:59:59", "2026-10-16 23:59:59", "2026-10-17 23:59:59", "2026-10-28 23:59:59"], - control_plan_themes_id= [3, 4, 5, 6], - control_plan_sub_themes_id=[7, 8, 9, 10], - control_plan_tags_id=[11, 12, 13, 14], - reportings_control_plan_sub_themes_id=[15, 16, 17, 18], - ) - -@pytest.fixture -def old_themes() -> pd.DataFrame: - return generate_themes_data( - ids=[991, 992, 993, 994], - names=["Thème 1", "Thème 2", "Thème 3", "Thème 4"], - parents_id=[991, 992, 992, 994], - started_at=["2025-10-15 00:00:00", "2025-10-16 00:00:00", "2025-10-17 00:00:00", "2025-10-18 00:00:00"], - ended_at=["2026-10-15 23:59:59", "2026-10-16 23:59:59", "2026-10-17 23:59:59", "2026-10-18 23:59:59"], - control_plan_themes_id= [3, 4, 5, 6], - control_plan_sub_themes_id=[7, 8, 9, 10], - control_plan_tags_id=[11, 12, 13, 14], - reportings_control_plan_sub_themes_id=[15, 16, 17, 18], - ) - - -def test_load_new_themes(reset_test_data, old_themes): - load_new_themes.run(old_themes) - loaded_themes = read_query( - "monitorenv_remote", - """SELECT - id, name, parent_id, started_at, ended_at, control_plan_themes_id, control_plan_sub_themes_id, control_plan_tags_id, reportings_control_plan_sub_themes_id - FROM public.themes - WHERE id IN (991, 992, 993, 994) - ORDER BY id""" - ) - - pd.testing.assert_frame_equal(loaded_themes, old_themes) - - -def test_update_new_themes(reset_test_data, new_themes, old_themes): - load( - old_themes, - table_name="themes", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="append", - ) - update_themes.run(new_themes) - updated_themes = read_query( - "monitorenv_remote", - """SELECT - id, name, parent_id, started_at, ended_at, control_plan_themes_id, control_plan_sub_themes_id, control_plan_tags_id, reportings_control_plan_sub_themes_id - FROM public.themes - WHERE id IN (991, 992, 993, 994) - ORDER BY id""" - ) - for col in ["id", "parent_id", "control_plan_themes_id", "control_plan_sub_themes_id", "control_plan_tags_id", "reportings_control_plan_sub_themes_id"]: - updated_themes[col] = updated_themes[col].astype("Int64") - - pd.testing.assert_frame_equal(updated_themes, new_themes) - - -""" Tags """ -def generate_tags_data(ids, names, parents_id, started_at, ended_at): - return pd.DataFrame({ - "id": ids, - "name": names, - "parent_id": parents_id, - "started_at": pd.to_datetime(started_at), - "ended_at": pd.to_datetime(ended_at), - }) - -@pytest.fixture -def new_tags() -> pd.DataFrame: - return generate_tags_data( - ids=[1, 2, 3, 4], - names=["Tag 1", "Tag 2_new", "Tag 3", "Tag 4"], - parents_id=[1, 1, 2, 2], - started_at=["2025-10-15 00:00:00", "2025-10-16 00:00:00", "2025-10-19 00:00:00", "2025-10-18 00:00:00"], - ended_at=["2026-10-15 23:59:59", "2026-10-16 23:59:59", "2026-10-17 23:59:59", "2026-10-28 23:59:59"], - ) - -@pytest.fixture -def old_tags() -> pd.DataFrame: - return generate_tags_data( - ids=[1, 2, 3, 4], - names=["Tag 1", "Tag 2", "Tag 3", "Tag 4"], - parents_id=[1, 1, 2, 4], - started_at=["2025-10-15 00:00:00", "2025-10-16 00:00:00", "2025-10-17 00:00:00", "2025-10-18 00:00:00"], - ended_at=["2026-10-15 23:59:59", "2026-10-16 23:59:59", "2026-10-17 23:59:59", "2026-10-18 23:59:59"], - ) - - -def test_load_new_tags(reset_test_data, old_tags): - load_new_tags.run(old_tags) - loaded_tags = read_query( - "monitorenv_remote", - """SELECT - id, name, parent_id, started_at, ended_at - FROM public.tags - ORDER BY id""" - ) - - pd.testing.assert_frame_equal(loaded_tags, old_tags) - - -def test_update_new_tags(reset_test_data, new_tags, old_tags): - load( - old_tags, - table_name="tags", - schema="public", - db_name="monitorenv_remote", - logger=prefect.context.get("logger"), - how="append", - ) - update_tags.run(new_tags) - updated_tags = read_query( - "monitorenv_remote", - """SELECT - id, name, parent_id, started_at, ended_at - FROM public.tags - ORDER BY id""" - ) - for col in ["id", "parent_id"]: - updated_tags[col] = updated_tags[col].astype("Int64") - pd.testing.assert_frame_equal(updated_tags, new_tags) - -def test_flow_themes_and_tags(create_cacem_tables): - state = flow.run() - assert state.is_successful() \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/test_three_hundred_meters_areas.py b/datascience/tests/test_pipeline/test_flows/test_three_hundred_meters_areas.py deleted file mode 100644 index f6b43b814b..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_three_hundred_meters_areas.py +++ /dev/null @@ -1,24 +0,0 @@ -from geopandas.testing import assert_geodataframe_equal - -from src.pipeline.flows.three_hundred_meters_areas import extract_three_hundred_meters_areas, load_three_hundred_meters_areas, flow -from src.read_query import read_query - - -def test_load_three_hundred_meters_areas(create_cacem_tables): - three_hundred_meters_areas = extract_three_hundred_meters_areas.run() - assert three_hundred_meters_areas.shape[0] == 1 - - load_three_hundred_meters_areas.run(three_hundred_meters_areas) - imported_three_hundred_meters_areas = read_query( - db="monitorenv_remote", - query="SELECT id, geom, secteur FROM three_hundred_meters_areas", - backend="geopandas", - geom_col="geom", - crs=4326, - ) - - assert_geodataframe_equal(three_hundred_meters_areas, imported_three_hundred_meters_areas) - -def test_flow_three_hundred_meters_areas(create_cacem_tables): - state = flow.run() - assert state.is_successful() diff --git a/datascience/tests/test_pipeline/test_flows/test_update_departments_and_facades.py b/datascience/tests/test_pipeline/test_flows/test_update_departments_and_facades.py deleted file mode 100644 index 1a02b37dde..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_update_departments_and_facades.py +++ /dev/null @@ -1,77 +0,0 @@ -from uuid import UUID - -import pandas as pd - -from src.pipeline.flows.update_departments_and_facades import flow -from src.read_query import read_query -from tests.mocks import mock_check_flow_not_running - -flow.replace( - flow.get_tasks("check_flow_not_running")[0], mock_check_flow_not_running -) - - -def test_flow(reset_test_data): - - expected_initial_missions = pd.DataFrame( - { - "id": [-95690, -95689, 12, 13, 19, 20], - "facade": [None, None, "NAMO", "NAMO", "NAMO", "MED"], - } - ) - - expected_final_missions = pd.DataFrame( - { - "id": [-95690, -95689, 12, 13, 19, 20], - "facade": [None, None, "Facade B", "Facade A", None, None], - } - ) - - expected_initial_actions = pd.DataFrame( - { - "id": [ - UUID("88713755-3966-4ca4-ae18-10cab6249485"), - UUID("b05d96b8-387f-4599-bff0-cd7dab71dfb8"), - UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), - UUID("dedbd2c2-10f5-4d75-8fe9-c50db2ae5d0b"), - UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), - ], - "facade": [None, None, None, None, None], - "department": [None, None, None, None, None], - } - ) - - expected_final_actions = pd.DataFrame( - { - "id": [ - UUID("88713755-3966-4ca4-ae18-10cab6249485"), - UUID("b05d96b8-387f-4599-bff0-cd7dab71dfb8"), - UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), - UUID("dedbd2c2-10f5-4d75-8fe9-c50db2ae5d0b"), - UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), - ], - "facade": [None, "Facade B", "Facade B", None, "Facade B"], - "department": [None, "44", "56", None, "85"], - } - ) - - missions_query = "SELECT id, facade FROM missions ORDER BY id" - initial_missions = read_query("monitorenv_remote", missions_query) - - actions_query = ( - "SELECT id, facade, department FROM env_actions ORDER BY id" - ) - initial_actions = read_query("monitorenv_remote", actions_query) - - flow.schedule = None - state = flow.run() - assert state.is_successful() - - final_missions = read_query("monitorenv_remote", missions_query) - final_actions = read_query("monitorenv_remote", actions_query) - - pd.testing.assert_frame_equal(expected_initial_missions, initial_missions) - pd.testing.assert_frame_equal(expected_final_missions, final_missions) - - pd.testing.assert_frame_equal(expected_initial_actions, initial_actions) - pd.testing.assert_frame_equal(expected_final_actions, final_actions) diff --git a/datascience/tests/test_pipeline/test_flows/test_vessel_repository.py b/datascience/tests/test_pipeline/test_flows/test_vessel_repository.py deleted file mode 100644 index a27b16936c..0000000000 --- a/datascience/tests/test_pipeline/test_flows/test_vessel_repository.py +++ /dev/null @@ -1,131 +0,0 @@ -from pathlib import Path -import pandas as pd -from src.read_query import read_query - -from src.pipeline.flows.vessel_repository import delete_files, parse_xml_and_load, get_xsd_schema, flow -from tests.mocks import mock_delete_files, mock_get_xsd_file, mock_get_xml_files - - -from config import ( - TEST_DATA_LOCATION, -) - -def test_delete_file(tmp_path): - tmp_file = tmp_path / "dummy.txt" - tmp_file.write_text("data") - - assert tmp_file.exists() - - delete_files.run([str(tmp_file)]) - - assert not tmp_file.exists() - - -def test_parse_and_load(create_cacem_tables, reset_test_data): - xml_path = TEST_DATA_LOCATION / "vessel_xml" / "vessel_repository.xml" - xsd_path = TEST_DATA_LOCATION / "vessel_xml" / "vessel_repository.xsd" - schema = get_xsd_schema.run(xsd_path) - - parse_xml_and_load(xml_path, schema, batch_size=1) - - expected_df = pd.DataFrame( - [{ - "ship_id": "1111111", - "status": "A", - "category": "PRO", - "is_banned": "No", - "imo_number": "1234567", - "mmsi_number": "123456789", - "call_sign": "0000001", - "ship_name": "ShipName1", - "flag": "FRA", - "port_of_registry": "CHERBOURG", - "immatriculation": "999999", - "professional_type": "Porte-Conteneur", - "leisure_type": None, - "commercial_name": "UICKSILVER", - "length": "32", - "owner_date_of_information": "2019-05-29T00:00:00.000", - "owner_last_name": "NOM 1", - "owner_first_name": "PRENOM 1", - "owner_date_of_birth": "1977-08-19", - "owner_postal_address": "17 AVENUE DESAVENUES 14000 CAEN", - "owner_phone": "0987654321", - "owner_email": "email1@gmail.com", - "owner_nationality": "FRA", - "owner_company_name": "COMPANY 1", - "owner_business_segment": "93.29Z", - "owner_legal_status": "1234", - "owner_start_date": "2019-05-29", - "batch_id": "1", - "row_number": "1" - }, - { - "ship_id": "2222222", - "status": "A", - "category": "PLA", - "is_banned": "No", - "imo_number": "7654321", - "mmsi_number": "987654321", - "call_sign": "0000002", - "ship_name": "ShipName2", - "flag": "FRA", - "port_of_registry": "DZAOUDZI", - "immatriculation": "888888", - "professional_type": None, - "leisure_type": "Navire a passagers", - "commercial_name": None, - "length": "9.6", - "owner_date_of_information": "2019-05-29T00:00:00.000", - "owner_last_name": "NOM 2", - "owner_first_name": "PRENOM 2", - "owner_date_of_birth": "1977-08-19", - "owner_postal_address": "17 RUE DESRUES 14000 CAEN", - "owner_phone": "0123456789", - "owner_email": "email2@gmail.com", - "owner_nationality": "FRA", - "owner_company_name": "COMPANY 2", - "owner_business_segment": "93.87Z", - "owner_legal_status": "5678", - "owner_start_date": "2019-05-29", - "batch_id": "1", - "row_number": "1" - }]) - - - imported_vessels = read_query( - "monitorenv_remote", - # Cast boolean is_banned to Yes / No - """SELECT ship_id, status, category, CASE WHEN is_banned IS TRUE THEN 'Yes' ELSE 'No' END as is_banned, imo_number, mmsi_number, call_sign, ship_name, flag, port_of_registry, - immatriculation,professional_type, leisure_type, commercial_name, length, owner_date_of_information, owner_last_name, owner_first_name, owner_date_of_birth, owner_postal_address, - owner_phone, owner_email, owner_nationality, owner_company_name, owner_business_segment, owner_legal_status, owner_start_date, batch_id, row_number - FROM vessels""" - ) - - # Cast owner_date_of_information to datetime to compare it - imported_vessels["owner_date_of_information"] = imported_vessels["owner_date_of_information"].apply(pd.to_datetime, errors="coerce") - expected_df["owner_date_of_information"] = expected_df["owner_date_of_information"].apply(pd.to_datetime, errors="coerce") - # Cast batch_id, row_number, length and ship_id to numeric to compare it - expected_df["batch_id"] = expected_df["batch_id"].apply(pd.to_numeric, errors="coerce") - imported_vessels["batch_id"] = imported_vessels["batch_id"].apply(pd.to_numeric, errors="coerce") - expected_df["row_number"] = expected_df["row_number"].apply(pd.to_numeric, errors="coerce") - imported_vessels["row_number"] = imported_vessels["row_number"].apply(pd.to_numeric, errors="coerce") - imported_vessels["ship_id"] = imported_vessels["ship_id"].apply(pd.to_numeric, errors="coerce") - expected_df["ship_id"] = expected_df["ship_id"].apply(pd.to_numeric, errors="coerce") - imported_vessels["ship_id"] = imported_vessels["ship_id"].apply(pd.to_numeric, errors="coerce") - expected_df["length"] = expected_df["length"].apply(pd.to_numeric, errors="coerce") - imported_vessels["length"] = imported_vessels["length"].apply(pd.to_numeric, errors="coerce") - - - pd.testing.assert_frame_equal(expected_df, imported_vessels, check_dtype=False) - - -def test_flow_vessel_repository(create_cacem_tables, reset_test_data): - flow.replace(flow.get_tasks('get_xsd_file')[0], mock_get_xsd_file) - flow.replace(flow.get_tasks('get_xml_files')[0], mock_get_xml_files) - while flow.get_tasks("delete_files"): - flow.replace(flow.get_tasks('delete_files')[0], mock_delete_files) - - state = flow.run() - - assert state.is_successful() \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows_config.py b/datascience/tests/test_pipeline/test_flows_config.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/tests/test_pipeline/test_helpers/__init__.py b/datascience/tests/test_pipeline/test_helpers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/tests/test_pipeline/test_helpers/test_dates.py b/datascience/tests/test_pipeline/test_helpers/test_dates.py deleted file mode 100644 index fc91977981..0000000000 --- a/datascience/tests/test_pipeline/test_helpers/test_dates.py +++ /dev/null @@ -1,164 +0,0 @@ -import unittest -from datetime import datetime, timedelta - -import numpy as np -import pandas as pd - -from src.pipeline.helpers.dates import ( - Period, - get_datetime_intervals, - make_periods, -) - - -class TestHelpersDatetime(unittest.TestCase): - def test_get_datetime_intervals(self): - - s = pd.Series( - [ - datetime(2021, 1, 1, 0, 0, 0), - datetime(2021, 1, 1, 0, 10, 0), - datetime(2021, 1, 1, 0, 0, 10), - datetime(2021, 1, 1, 10, 0, 0), - datetime(2021, 1, 1, 10, 0, 25), - ], - index=[1, 129, 4, 5, 0], - ) - - # Test default unit and computation method - intervals = get_datetime_intervals(s) - expected_intervals = pd.Series( - [ - pd.NaT, - timedelta(minutes=10), - timedelta(minutes=-10, seconds=10), - timedelta(hours=10, seconds=-10), - timedelta(seconds=25), - ], - index=[1, 129, 4, 5, 0], - ) - - pd.testing.assert_series_equal(intervals, expected_intervals) - - # Test unit 'h' and computation method 'forward' - intervals = get_datetime_intervals(s, unit="h", how="forward") - expected_intervals = pd.Series( - [ - 0.16666666666666666, - -0.1638888888888889, - 9.997222222222222, - 0.006944444444444445, - np.nan, - ], - index=[1, 129, 4, 5, 0], - ) - - pd.testing.assert_series_equal(intervals, expected_intervals) - - # Test unit 'min' and computation method 'backward' - intervals = get_datetime_intervals(s, unit="min", how="backward") - expected_intervals = pd.Series( - [ - np.nan, - 10, - -9.833333333333334, - 599.8333333333334, - 0.4166666666666667, - ], - index=[1, 129, 4, 5, 0], - ) - - pd.testing.assert_series_equal(intervals, expected_intervals) - - # Test unit 's' and computation method 'backward' - intervals = get_datetime_intervals(s, unit="s", how="backward") - expected_intervals = pd.Series( - [ - np.nan, - 600, - -590, - 35990, - 25, - ], - index=[1, 129, 4, 5, 0], - ) - - pd.testing.assert_series_equal(intervals, expected_intervals) - - # Test unit 'incorrect' - with self.assertRaises(ValueError): - get_datetime_intervals(s, unit="incorrect") - - # Test how 'incorrect' - with self.assertRaises(ValueError): - get_datetime_intervals(s, how="incorrect") - - def test_make_periods(self): - with self.assertRaises(ValueError): - make_periods( - start_datetime_utc=datetime(2021, 12, 1), - end_datetime_utc=datetime(2021, 12, 10), - period_duration=timedelta(days=1), - overlap=timedelta(days=1), - ) - - with self.assertRaises(ValueError): - make_periods( - start_datetime_utc=datetime(2021, 12, 1), - end_datetime_utc=datetime(2021, 12, 10), - period_duration=timedelta(days=1), - overlap=timedelta(days=2), - ) - - with self.assertRaises(ValueError): - make_periods( - start_datetime_utc=datetime(2021, 12, 1), - end_datetime_utc=datetime(2021, 11, 10), - period_duration=timedelta(days=1), - overlap=timedelta(hours=4), - ) - - # Test with overlap specified - periods = make_periods( - start_datetime_utc=datetime(2021, 12, 1), - end_datetime_utc=datetime(2021, 12, 3), - period_duration=timedelta(days=1), - overlap=timedelta(hours=4), - ) - - expected_periods = [ - Period( - start=datetime(2021, 12, 1, 0, 0), - end=datetime(2021, 12, 2, 0, 0), - ), - Period( - start=datetime(2021, 12, 1, 20, 0), - end=datetime(2021, 12, 2, 20, 0), - ), - Period( - start=datetime(2021, 12, 2, 16, 0), - end=datetime(2021, 12, 3, 0, 0), - ), - ] - - self.assertEqual(periods, expected_periods) - - # Test without overlap specified - periods = make_periods( - start_datetime_utc=datetime(2021, 12, 1), - end_datetime_utc=datetime(2021, 12, 3), - period_duration=timedelta(days=1), - ) - - expected_periods = [ - Period( - start=datetime(2021, 12, 1, 0, 0), - end=datetime(2021, 12, 2, 0, 0), - ), - Period( - start=datetime(2021, 12, 2, 0, 0), - end=datetime(2021, 12, 3, 0, 0), - ), - ] - - self.assertEqual(periods, expected_periods) diff --git a/datascience/tests/test_pipeline/test_processing.py b/datascience/tests/test_pipeline/test_processing.py deleted file mode 100644 index c78c987dc8..0000000000 --- a/datascience/tests/test_pipeline/test_processing.py +++ /dev/null @@ -1,863 +0,0 @@ -import datetime -import logging -import unittest -from unittest.mock import Mock, patch - -import numpy as np -import pandas as pd -import pytz -from sqlalchemy import Column, Integer, MetaData, Table - -from src.pipeline.processing import ( - array_equals_row_on_window, - back_propagate_ones, - coalesce, - concatenate_columns, - concatenate_values, - df_to_dict_series, - df_values_to_json, - df_values_to_psql_arrays, - drop_duplicates_by_decreasing_priority, - drop_rows_already_in_table, - get_unused_col_name, - is_a_value, - join_on_multiple_keys, - left_isin_right_by_decreasing_priority, - prepare_df_for_loading, - rows_belong_to_sequence, - to_json, - to_pgarr, - zeros_ones_to_bools, -) - - -class TestProcessingMethods(unittest.TestCase): - def test_get_unused_col_name(self): - - self.assertEqual( - get_unused_col_name("id", pd.DataFrame({"id": [1, 2, 3]})), "id_0" - ) - - self.assertEqual( - get_unused_col_name( - "id", pd.DataFrame({"id": [1, 2, 3], "id_0": [1, 2, 3]}) - ), - "id_1", - ) - - self.assertEqual( - get_unused_col_name("id", pd.DataFrame({"idx": [1, 2, 3]})), "id" - ) - - def test_is_a_value(self): - self.assertTrue(is_a_value(1)) - self.assertTrue(is_a_value("")) - self.assertTrue(is_a_value("a")) - self.assertFalse(is_a_value(None)) - self.assertFalse(is_a_value(np.nan)) - - def test_concatenate_values(self): - row = pd.Series(["a", "a", 1, 2, None, "abc", 3, 2, None]) - self.assertEqual(concatenate_values(row), ["a", 1, 2, "abc", 3]) - - def test_concatenate_columns(self): - test_df_1 = pd.DataFrame( - {"a": [1, "a", 2], "b": ["a", "b", None], "c": ["a", "c", 2]} - ) - - test_df_2 = pd.DataFrame( - { - "a": [None, None, None], - "b": [None, None, None], - "c": [None, None, None], - } - ) - - res1 = concatenate_columns(test_df_1, ["a"]) - self.assertEqual(res1[0], [1]) - self.assertEqual(res1[1], ["a"]) - self.assertEqual(res1[2], [2]) - - res2 = concatenate_columns(test_df_1, ["a", "b", "c"]) - self.assertEqual(res2[0], [1, "a"]) - self.assertEqual(res2[1], ["a", "b", "c"]) - self.assertEqual(res2[2], [2]) - - res3 = concatenate_columns(test_df_2, ["a", "c"]) - self.assertEqual(res3[0], []) - self.assertEqual(res3[1], []) - self.assertEqual(res3[2], []) - - def test_combine_overlapping_columns(self): - df = pd.DataFrame( - { - "a": [1, 2, 3, None, np.nan, None, np.nan], - "b": [None, None, "a", "b", None, None, np.nan], - "c": ["c", None, None, "c", "c", None, np.nan], - "d": ["d", "d", "d", "d", "d", None, np.nan], - } - ) - - res = coalesce(df[["b", "c", "a"]]) - self.assertEqual(res.values[0], "c") - self.assertEqual(res.values[1], 2) - self.assertEqual(res.values[2], "a") - self.assertEqual(res.values[3], "b") - self.assertEqual(res.values[4], "c") - self.assertEqual(res.values[5], None) - self.assertEqual(res.values[6], None) - - def test_df_to_dict_series(self): - df = pd.DataFrame( - data={ - "a": [1, 2, 3, 4, 5], - "b": ["a", 1.0, None, np.nan, [1, 2, 3]], - "c": [None, None, None, None, None], - "d": [ - 1, - [1, "a", None], - [{"a": 1, "b": None, "c": np.nan}], - {"a": 1, "b": [1, np.nan, None, ["a", "b"]]}, - None, - ], - }, - index=pd.Index([0, 1, 4, 123, 3]), - ) - - expected_values = [ - {"a": 1, "b": "a", "c": None, "d": 1}, - {"a": 2, "b": 1.0, "c": None, "d": [1, "a", None]}, - { - "a": 3, - "b": None, - "c": None, - "d": [{"a": 1, "b": None, "c": None}], - }, - { - "a": 4, - "b": None, - "c": None, - "d": {"a": 1, "b": [1, None, None, ["a", "b"]]}, - }, - {"a": 5, "b": [1, 2, 3], "c": None, "d": None}, - ] - - res = df_to_dict_series(df, result_colname="my_column_name") - - self.assertTrue(isinstance(res, pd.Series)) - self.assertEqual(res.name, "my_column_name") - self.assertTrue((res.index == [0, 1, 4, 123, 3]).all()) - self.assertEqual(res.values.tolist(), expected_values) - - def test_zeros_ones_to_bools(self): - # Test with DataFrame - df = pd.DataFrame( - { - "ints_0_1": [0, 1, 0, 1, 1], - "ints_0_1_2": [0, 1, 0, 2, 1], - "floats_0_1": [0.0, 1.0, 0.0, 1.0, 1.0], - "floats_0_1_2": [0.0, 1.0, 0.0, 2.0, 1.0], - "str_0_1": ["0", "1", "0", "1", "1"], - "str_0_1_2": ["0", "1", "0", "2", "1"], - "ints_0_1_nan": [0, 1, np.nan, 1, None], - "ints_0_1_2_nan": [0, 1, np.nan, 2, None], - "str_0_1_nan": ["0", "1", np.nan, "1", None], - "str_0_1_2_nan": ["0", "1", np.nan, "2", None], - "all_nan": [np.nan, np.nan, np.nan, np.nan, np.nan], - "all_none": [None, None, None, None, None], - } - ) - - res = zeros_ones_to_bools(df) - expected_res = pd.DataFrame( - { - "ints_0_1": [False, True, False, True, True], - "ints_0_1_2": [False, True, False, True, True], - "floats_0_1": [False, True, False, True, True], - "floats_0_1_2": [False, True, False, True, True], - "str_0_1": [False, True, False, True, True], - "str_0_1_2": [False, True, False, True, True], - "ints_0_1_nan": [False, True, np.nan, True, np.nan], - "ints_0_1_2_nan": [False, True, np.nan, True, np.nan], - "str_0_1_nan": [False, True, np.nan, True, np.nan], - "str_0_1_2_nan": [False, True, np.nan, True, np.nan], - "all_nan": [np.nan, np.nan, np.nan, np.nan, np.nan], - "all_none": [np.nan, np.nan, np.nan, np.nan, np.nan], - } - ) - - pd.testing.assert_frame_equal(res, expected_res) - - # Test with Series - s = pd.Series( - [0, 0.0, "0", "0.0", None, np.nan, 1, 1.0, 23.1, -2.3, "1", "12.1"] - ) - res = zeros_ones_to_bools(s) - expected_res = pd.Series( - [ - False, - False, - False, - False, - np.nan, - np.nan, - True, - True, - True, - True, - True, - True, - ] - ) - - pd.testing.assert_series_equal(res, expected_res) - - def test_to_pgarr(self): - a = [1, 2, 3] - b = ["a", "b", "c"] - c = [1, "a", None] - d = [] - e = None - - res_a = to_pgarr(a) - res_b = to_pgarr(b) - res_c = to_pgarr(c) - res_d = to_pgarr(d) - with self.assertRaises(ValueError): - to_pgarr(e) - - res_e = to_pgarr(e, handle_errors=True) - - self.assertEqual(res_a, "{1,2,3}") - self.assertEqual(res_b, "{a,b,c}") - self.assertEqual(res_c, "{1,a,None}") - self.assertEqual(res_d, "{}") - self.assertIsNone(res_e) - - def test_to_json(self): - - # Test basic dicts and lists serialization - a = [1, 2, 3] - b = {"a": 1, "b": 2} - - # Test None and np.nan serialization - c = None - d = np.nan - e = [1, 2, 3, None, np.nan] - f = [1, 2, {"a": 1, "b": None, "c": np.nan}] - e = {"a": 1, 2: "b"} - f = {"a": {"b": [1, None, np.nan], "c": 4}, "b": 2} - - # Test numpy array serialization - g = np.array([1, 2, 3]) - h = np.array([[1, 2, 3], [4, 5, 6]]) - - # Test date and datetime serialization - i = datetime.datetime(2020, 3, 11, 20, 5, 12) - utc_tz = pytz.timezone("utc") - j = utc_tz.localize(datetime.datetime(2020, 3, 11, 20, 5, 12)) - est_tz = pytz.timezone("est") - k = est_tz.localize(datetime.datetime(2020, 3, 11, 20, 5, 12)) - m = datetime.date(2020, 12, 5) - - # Test nested dict containing None, np.nan, pandas `NaT` - n = { - "a": { - "int": 1, - "None": None, - "np.nan": np.nan, - "pandas NaT": pd._libs.tslibs.nattype.NaTType(), - "numpy array": np.array([1, 2, 3]), - "date": datetime.date(2020, 12, 5), - "datetime": datetime.datetime(2020, 3, 11, 20, 5, 12), - "datetime_tz_est": est_tz.localize( - datetime.datetime(2020, 3, 11, 20, 5, 12) - ), - } - } - - res_a = to_json(a) - res_b = to_json(b) - res_c = to_json(c) - res_d = to_json(d) - res_e = to_json(e) - res_f = to_json(f) - res_e = to_json(e) - res_f = to_json(f) - res_g = to_json(g) - res_h = to_json(h) - res_i = to_json(i) - res_j = to_json(j) - res_k = to_json(k) - res_m = to_json(m) - res_n = to_json(n) - - self.assertEqual(res_a, "[1, 2, 3]") - self.assertEqual(res_b, '{"a": 1, "b": 2}') - self.assertEqual(res_c, "null") - self.assertEqual(res_d, "null") - self.assertEqual(res_e, '{"a": 1, "2": "b"}') - self.assertEqual( - res_f, '{"a": {"b": [1, null, null], "c": 4}, "b": 2}' - ) - self.assertEqual(res_e, '{"a": 1, "2": "b"}') - self.assertEqual( - res_f, '{"a": {"b": [1, null, null], "c": 4}, "b": 2}' - ) - self.assertEqual(res_g, "[1, 2, 3]") - self.assertEqual(res_h, "[[1, 2, 3], [4, 5, 6]]") - self.assertEqual(res_i, '"2020-03-11T20:05:12Z"') - self.assertEqual(res_j, '"2020-03-11T20:05:12Z"') - self.assertEqual(res_k, '"2020-03-12T01:05:12Z"') - self.assertEqual(res_m, '"2020-12-05"') - self.assertEqual( - res_n, - '{"a": {"int": 1, "None": null, "np.nan": null, "pandas NaT": null, ' - '"numpy array": [1, 2, 3], "date": "2020-12-05", ' - '"datetime": "2020-03-11T20:05:12Z", ' - '"datetime_tz_est": "2020-03-12T01:05:12Z"}}', - ) - - def test_df_values_to_json(self): - df = pd.DataFrame( - [ - [1, 2, 3, 4, 5], - [[1, 2, 3], {"a": 1, "b": None}, None, np.nan, "null"], - [ - {"a": [1, 2, 3], "b": {"a": 1, "b": None, "c": np.nan}}, - 2, - 3, - "null", - None, - ], - ], - columns=pd.Index(["a", "b", "c", "d", "e"]), - ) - - expected_values = [ - ["1", "2", "3.0", "4", "5"], - ["[1, 2, 3]", '{"a": 1, "b": null}', "null", "null", '"null"'], - [ - '{"a": [1, 2, 3], "b": {"a": 1, "b": null, "c": null}}', - "2", - "3.0", - '"null"', - "null", - ], - ] - - res = df_values_to_json(df) - self.assertTrue(isinstance(res, pd.DataFrame)) - self.assertEqual(res.shape, (3, 5)) - self.assertEqual(list(res), ["a", "b", "c", "d", "e"]) - self.assertEqual(res.values.tolist(), expected_values) - - def test_df_values_to_psql_arrays(self): - df = pd.DataFrame( - { - "a": [1, 2, 3, 4], - "b": [[1, 2], ["a", "b"], [1, 3], ["a", "a"]], - "c": [ - "This string shouldn't be here", - np.nan, - ["", 5, " "], - None, - ], - } - ) - - with self.assertRaises(ValueError): - df_values_to_psql_arrays(df[["b", "c"]]) - res = df_values_to_psql_arrays( - df[["b", "c"]], handle_errors=True, value_on_error="caught" - ) - - self.assertEqual(list(res), ["b", "c"]) - # self.assertTrue((res["b"] == ["{1,2}", "{a,b}", "{1,3}", "{a,a}"]).all()) - # self.assertTrue((res["c"] == ["caught", "{}", "{5}", "{}"]).all()) - - expected_values = [ - ["{1,2}", "caught"], - ["{a,b}", "{}"], - ["{1,3}", "{5}"], - ["{a,a}", "{}"], - ] - - self.assertEqual(res.values.tolist(), expected_values) - - @patch("src.pipeline.processing.pd") - def test_drop_rows_already_in_table(self, mock_pandas): - - df = pd.DataFrame( - data=[ - [1, 2, 3, 4], - [1, "b", "c", "d"], - [2, "first_value", 0, None], - [2, "second_value_gets_dropped", None, 1], - [3, 2, 3, 4], - ], - columns=pd.Index(["df_id_column", "a", "b", "c"]), - ) - - meta = MetaData() - table = Table( - "my_test_table", meta, Column("table_id_column", Integer) - ) - mock_connection = Mock() - logger = logging.Logger("test_logger") - - mock_pandas.read_sql.return_value = pd.DataFrame( - [1], columns=pd.Index(["table_id_column"]) - ) - - res = drop_rows_already_in_table( - df=df, - df_column_name="df_id_column", - table=table, - table_column_name="table_id_column", - connection=mock_connection, - logger=logger, - ) - - expected_values = [[2, "first_value", 0, None], [3, 2, 3, 4]] - - self.assertTrue(isinstance(res, pd.DataFrame)) - self.assertEqual(res.shape, (2, 4)) - self.assertEqual(res.values.tolist(), expected_values) - - def test_prepare_df_for_loading(self): - df = pd.DataFrame( - columns=pd.Index( - [ - "id", - "column_1", - "pg_array_1", - "pg_array_2", - "json_1", - "json_2", - "int", - "timedelta", - "timedelta_only_nulls", - ] - ), - data=[ - [ - 1, - "some value", - [1, 2, 3], - ["a", "b", "c"], - {"a": 1, "b": 2}, - {"a": 1, "b": None}, - 2.0, - datetime.timedelta(days=1, seconds=21), - None, - ], - [ - 2, - "some other value", - [1, 2, 5], - [1, 5, 7], - {"a": 1, "b": None, "c": np.nan}, - { - "a": 1, - "b": [ - datetime.datetime(2021, 1, 23, 12, 56, 7), - np.nan, - ], - }, - np.nan, - None, - None, - ], - ], - ) - - logger = logging.Logger("test_logger") - - res = prepare_df_for_loading( - df, - logger, - pg_array_columns=["pg_array_1", "pg_array_2"], - handle_array_conversion_errors=True, - value_on_array_conversion_error="{}", - jsonb_columns=["json_1", "json_2"], - nullable_integer_columns=["int"], - timedelta_columns=["timedelta", "timedelta_only_nulls"], - ) - - expected_res = pd.DataFrame( - columns=pd.Index( - [ - "id", - "column_1", - "pg_array_1", - "pg_array_2", - "json_1", - "json_2", - "int", - "timedelta", - "timedelta_only_nulls", - ] - ), - data=[ - [ - 1, - "some value", - "{1,2,3}", - "{a,b,c}", - '{"a": 1, "b": 2}', - '{"a": 1, "b": null}', - "2", - "1 days 00:00:21", - None, - ], - [ - 2, - "some other value", - "{1,2,5}", - "{1,5,7}", - '{"a": 1, "b": null, "c": null}', - '{"a": 1, "b": ["2021-01-23T12:56:07Z", null]}', - None, - None, - None, - ], - ], - ) - - pd.testing.assert_frame_equal(res, expected_res) - - def test_join_on_multiple_keys(self): - left = pd.DataFrame( - { - "key_1": [1, 2, None, 4, None, 6, None, np.nan, "conflict"], - "key_2": ["a", None, "c", "d", "e", None, None, np.nan, None], - "key_3": ["A", "B", np.nan, "D", "E", None, None, np.nan, "H"], - "value_left_1": [9, 8, 7, 6, 5, 4, 3, 2, 42], - "value_left_2": [ - 90, - 80, - "70", - None, - 5.025, - "left", - 40, - 30, - 48, - ], - } - ) - - right = pd.DataFrame( - { - "key_1": [1, 2, 3, 4, 5, 7, np.nan, "conflicting"], - "key_2": ["a", None, "c", "ddd", np.nan, None, np.nan, None], - "key_3": ["A", "B", "C", "DDD", "E", None, np.nan, "H"], - "value_right": [ - "R1", - "R2", - "R3", - "R4", - "R5", - "right", - np.nan, - "ABC", - ], - } - ) - - # Test inner join - res_inner = join_on_multiple_keys( - left, right, on=["key_1", "key_2", "key_3"], how="inner" - ).fillna("null") - - expected_values = [ - [1.0, "a", "A", 9, 90, "R1"], - [2.0, "null", "B", 8, 80, "R2"], - [4.0, "d", "D", 6, "null", "R4"], - [3.0, "c", "C", 7, "70", "R3"], - [5.0, "e", "E", 5, 5.025, "R5"], - ] - - self.assertEqual(res_inner.values.tolist(), expected_values) - - # Test left join - res_left = join_on_multiple_keys( - left, right, on=["key_1", "key_2", "key_3"], how="left" - ).fillna("null") - - expected_values = [ - [1.0, "a", "A", 9, 90, "R1"], - [2.0, "null", "B", 8, 80, "R2"], - [4.0, "d", "D", 6, "null", "R4"], - [3.0, "c", "C", 7, "70", "R3"], - [5.0, "e", "E", 5, 5.025, "R5"], - [6.0, "null", "null", 4, "left", "null"], - ["null", "null", "null", 3, 40, "null"], - ["null", "null", "null", 2, 30, "null"], - ["conflict", "null", "H", 42, 48, "null"], - ] - - self.assertEqual(res_left.values.tolist(), expected_values) - - # Test right join - res_right = join_on_multiple_keys( - left, right, on=["key_1", "key_2", "key_3"], how="right" - ).fillna("null") - - expected_values = [ - [1.0, "a", "A", 9.0, 90, "R1"], - [2.0, "null", "B", 8.0, 80, "R2"], - [4.0, "d", "D", 6.0, "null", "R4"], - [3.0, "c", "C", 7.0, "70", "R3"], - [5.0, "e", "E", 5.0, 5.025, "R5"], - [7.0, "null", "null", "null", "null", "right"], - ["null", "null", "null", "null", "null", "null"], - ["conflicting", "null", "H", "null", "null", "ABC"], - ] - - self.assertEqual(res_right.values.tolist(), expected_values) - - # Test outer join - res_outer = join_on_multiple_keys( - left, right, on=["key_1", "key_2", "key_3"], how="outer" - ).fillna("null") - - expected_values = [ - [1.0, "a", "A", 9.0, 90, "R1"], - [2.0, "null", "B", 8.0, 80, "R2"], - [4.0, "d", "D", 6.0, "null", "R4"], - [3.0, "c", "C", 7.0, "70", "R3"], - [5.0, "e", "E", 5.0, 5.025, "R5"], - [6.0, "null", "null", 4.0, "left", "null"], - ["null", "null", "null", 3.0, 40, "null"], - ["null", "null", "null", 2.0, 30, "null"], - ["conflict", "null", "H", 42, 48, "null"], - [7.0, "null", "null", "null", "null", "right"], - ["null", "null", "null", "null", "null", "null"], - ["conflicting", "null", "H", "null", "null", "ABC"], - ] - - self.assertEqual(res_outer.values.tolist(), expected_values) - - def test_left_isin_right_by_decreasing_priority(self): - left = pd.DataFrame( - { - "cfr": ["A", "B", "C", "D", None, None, None, "H"], - "external_immatriculation": [ - "AA", - None, - None, - "DD", - "EE", - None, - None, - "HH", - ], - "ircs": ["AAA", None, "CCC", None, None, "FFF", None, "HHH"], - }, - index=pd.Index([1, 2, 5, 12, 4, 23, 11, 120]), - ) - - right = pd.DataFrame( - { - "cfr": [ - "A", - "A", - None, - "B", - "C", - "D", - "E", - None, - "no conflict F", - None, - "conflict H", - ], - "external_immatriculation": [ - "AA", - "AA", - "AA", - "BB", - None, - "DD", - "EE", - "EE", - None, - None, - "HH", - ], - "ircs": [ - "AAA", - "AAA", - "AAA", - None, - "no conflic CCC", - None, - None, - None, - "FFF", - None, - "HHH", - ], - } - ) - - res = left_isin_right_by_decreasing_priority(left, right) - - expected = pd.Series( - index=left.index, - data=[True, True, True, True, True, True, False, False], - name="isin_right", - ) - - pd.testing.assert_series_equal(res, expected) - - def test_drop_duplicates_by_decreasing_priority(self): - - df = pd.DataFrame( - { - "A": [1, 2, 3, None, 1, 2, 3, 4, None, 22, None, None, None], - "B": [1, 2, 3, None, 1, 22, 3, 4, None, 2, None, None, 6], - "C": [1, 2, 3, 4, 1, 2, 33, 4, None, 2, 2, 5, 6], - "D": [ - "W", - "h", - "a", - "t", - "e", - "v", - "e", - "r", - "d", - "a", - "t", - "a", - "a", - ], - }, - index=[1, 2, 4, 14, 4, 32, 41, 2, 9, 13, 31, 4, 7], - ) - - res = drop_duplicates_by_decreasing_priority( - df, subset=["A", "B", "C"] - ) - - expected_res = df.iloc[[0, 1, 2, 7, 9, 12, 11]] - - pd.testing.assert_frame_equal(res, expected_res) - - with self.assertRaises(TypeError): - drop_duplicates_by_decreasing_priority(df, {"not", "a", "list"}) - - with self.assertRaises(TypeError): - empty_list = [] - drop_duplicates_by_decreasing_priority(df, empty_list) - - def test_array_equals_row_on_window(self): - - arr = np.array( - [ - [True, True, True], - [True, True, False], - [True, True, False], - [True, True, True], - [True, True, False], - [True, False, False], - ] - ) - - row = np.array([True, True, False]) - - res = array_equals_row_on_window(arr, row, window_length=1) - expected_res = np.array([0.0, 1.0, 1.0, 0.0, 1.0, 0.0]) - np.testing.assert_array_equal(res, expected_res) - - res = array_equals_row_on_window(arr, row, window_length=2) - expected_res = np.array([np.nan, 0.0, 1.0, 0.0, 0.0, 0.0]) - np.testing.assert_array_equal(res, expected_res) - - res = array_equals_row_on_window(arr, row, window_length=3) - expected_res = np.array([np.nan, np.nan, 0.0, 0.0, 0.0, 0.0]) - np.testing.assert_array_equal(res, expected_res) - - res = array_equals_row_on_window(arr, row, window_length=7) - expected_res = np.array( - [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan] - ) - np.testing.assert_array_equal(res, expected_res) - - def test_back_propagate_ones(self): - - arr = np.array( - [0.0, np.nan, 1.0, 0.0, np.nan, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0] - ) - - # Test steps=0 - np.testing.assert_array_equal(arr, back_propagate_ones(arr, 0)) - - # Test steps=1 - res = back_propagate_ones(arr, 1) - expected_res = np.array( - [np.nan, 1.0, 1.0, np.nan, np.nan, 1.0, 1.0, 1.0, 1.0, 0.0, np.nan] - ) - np.testing.assert_array_equal(res, expected_res) - - # Test steps=2 - res = back_propagate_ones(arr, 2) - expected_res = np.array( - [1.0, 1.0, 1.0, np.nan, 1.0, 1.0, 1.0, 1.0, 1.0, np.nan, np.nan] - ) - np.testing.assert_array_equal(res, expected_res) - - def test_rows_belong_to_sequence(self): - row = np.array([False, True]) - - arr = np.array( - [ - [False, True], - [False, True], - [True, True], - [False, True], - [False, True], - ] - ) - res = rows_belong_to_sequence(arr, row, 2) - expected_res = np.array([1.0, 1.0, 0.0, 1.0, 1.0]) - np.testing.assert_array_equal(res, expected_res) - - arr = np.array( - [ - [False, True], - [True, True], - [True, True], - [False, True], - [False, True], - ] - ) - res = rows_belong_to_sequence(arr, row, 2) - expected_res = np.array([np.nan, 0.0, 0.0, 1.0, 1.0]) - np.testing.assert_array_equal(res, expected_res) - - arr = np.array( - [ - [True, True], - [True, True], - [True, True], - [False, True], - [False, True], - [False, False], - ] - ) - res = rows_belong_to_sequence(arr, row, 2) - expected_res = np.array([0.0, 0.0, 0.0, 1.0, 1.0, 0.0]) - np.testing.assert_array_equal(res, expected_res) - - res = rows_belong_to_sequence(arr, row, 7) - expected_res = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - np.testing.assert_array_equal(res, expected_res) - - row = np.array([True, True]) - res = rows_belong_to_sequence(arr, row, 7) - expected_res = np.array([np.nan, np.nan, np.nan, 0.0, 0.0, 0.0]) - np.testing.assert_array_equal(res, expected_res) diff --git a/datascience/tests/test_pipeline/test_shared_tasks/__init__.py b/datascience/tests/test_pipeline/test_shared_tasks/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/datascience/tests/test_pipeline/test_shared_tasks/test_datagouv.py b/datascience/tests/test_pipeline/test_shared_tasks/test_datagouv.py deleted file mode 100644 index 442c2dd3c9..0000000000 --- a/datascience/tests/test_pipeline/test_shared_tasks/test_datagouv.py +++ /dev/null @@ -1,123 +0,0 @@ -from io import BytesIO -from unittest.mock import patch - -import fiona -import geopandas as gpd -import pandas as pd - -from src.pipeline.shared_tasks.datagouv import ( - api_url, - get_csv_file_object, - get_geopackage_file_object, - update_resource, -) -from tests.test_pipeline.test_utils import make_square_multipolygon - - -def test_api_url(): - assert api_url("/some/path/") == "https://www.data.gouv.fr/api/1/some/path/" - - -@patch("src.pipeline.shared_tasks.datagouv.requests") -def test_update_resource(mock_requests): - resource = BytesIO(b"some file object") - - update_resource.run( - dataset_id="123", - resource_id="666", - resource_title="File title", - resource=resource, - mock_update=False, - ) - - assert len(mock_requests.method_calls) == 1 - - mock_requests.post.assert_called_once_with( - "https://www.data.gouv.fr/api/1/datasets/123/resources/666/upload/", - files={"file": ("File title", resource)}, - headers={"X-API-KEY": "datagouv_api_key"}, - proxies={ - "http": "http://some.ip.address:port", - "https": "http://some.ip.address:port", - }, - ) - - -@patch("src.pipeline.shared_tasks.datagouv.requests") -def test_update_resource_hen_mock_update_is_true(mock_requests): - resource = BytesIO(b"some file object") - - update_resource.run( - dataset_id="123", - resource_id="666", - resource_title="File title", - resource=resource, - mock_update=True, - ) - - mock_requests.post.assert_not_called() - - -def test_get_csv_file_object(): - input_df = pd.DataFrame( - { - "ints": [1, 2, 3], - "strings": ["a", "b", "c"], - "floats": [1.0, 2.3, 10.5], - "nullable_ints": [1, 2, None], - "nullable_strings": ["a", "b", None], - "nullable_floats": [1.0, 2.3, None], - } - ) - - file_object = get_csv_file_object.run(input_df) - - assert isinstance(file_object, BytesIO) - - df_from_file_object = pd.read_csv(file_object) - pd.testing.assert_frame_equal( - df_from_file_object.convert_dtypes(), input_df.convert_dtypes() - ) - - -def test_get_geopackage_file_object_with_layers(): - input_gdf = gpd.GeoDataFrame( - { - "ints": [1, 2, 3, 4, 5], - "strings": ["a", "b", "a", "a", None], - "floats": [1.0, 2.3, 10.5, 11.2, -6.56], - "geometry": [ - make_square_multipolygon(0, 0, 10, 10), - make_square_multipolygon(-10, 45, 180, 5), - make_square_multipolygon(-110, 60, 10, 10), - None, - make_square_multipolygon(-105, 62, 10, 10), - ], - } - ) - - layers = "strings" - - file_object = get_geopackage_file_object.run(input_gdf, layers=layers) - - assert isinstance(file_object, BytesIO) - - layer_1, layer_2 = ["a", "b"] - assert fiona.listlayers(file_object) == [layer_1, layer_2] - - file_object.seek(0) - gdf_from_file_object = gpd.read_file(file_object, driver="GPKG", layer=layer_1) - print(gdf_from_file_object.to_markdown(index=False)) - print("*" * 88) - pd.testing.assert_frame_equal( - gdf_from_file_object, - input_gdf[input_gdf[layers] == layer_1].reset_index(drop=True), - ) - - file_object.seek(0) - gdf_from_file_object = gpd.read_file(file_object, driver="GPKG", layer=layer_2) - print(gdf_from_file_object.to_markdown(index=False)) - pd.testing.assert_frame_equal( - gdf_from_file_object, - input_gdf[input_gdf[layers] == layer_2].reset_index(drop=True), - ) diff --git a/datascience/tests/test_pipeline/test_shared_tasks/test_dates.py b/datascience/tests/test_pipeline/test_shared_tasks/test_dates.py deleted file mode 100644 index 3cd09c7695..0000000000 --- a/datascience/tests/test_pipeline/test_shared_tasks/test_dates.py +++ /dev/null @@ -1,37 +0,0 @@ -import unittest -from datetime import datetime -from unittest.mock import patch - -from src.pipeline.helpers.dates import Period -from src.pipeline.shared_tasks.dates import make_periods - - -class TestSharedTasksDates(unittest.TestCase): - @patch("src.pipeline.shared_tasks.dates.datetime") - def test_make_periods(self, mock_datetime): - - mock_datetime.utcnow.return_value = datetime(2021, 10, 5) - - periods = make_periods.run( - start_hours_ago=72, - end_hours_ago=0, - minutes_per_chunk=1560, - chunk_overlap_minutes=120, - ) - - expected_periods = [ - Period( - start=datetime(2021, 10, 2, 0, 0), - end=datetime(2021, 10, 3, 2, 0), - ), - Period( - start=datetime(2021, 10, 3, 0, 0), - end=datetime(2021, 10, 4, 2, 0), - ), - Period( - start=datetime(2021, 10, 4, 0, 0), - end=datetime(2021, 10, 5, 0, 0), - ), - ] - - self.assertEqual(periods, expected_periods) diff --git a/datascience/tests/test_pipeline/test_shared_tasks/test_geometries.py b/datascience/tests/test_pipeline/test_shared_tasks/test_geometries.py deleted file mode 100644 index 360343bc55..0000000000 --- a/datascience/tests/test_pipeline/test_shared_tasks/test_geometries.py +++ /dev/null @@ -1,21 +0,0 @@ -from shapely.geometry import MultiPolygon - -def make_square_multipolygon( - init_lon, - init_lat, - width, - height, -): - return MultiPolygon( - [ - ( - ( - (init_lon, init_lat), - (init_lon + width, init_lat), - (init_lon + width, init_lat + height), - (init_lon, init_lat + height), - ), - [], - ) - ] - ) \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_shared_tasks/test_update_queries.py b/datascience/tests/test_pipeline/test_shared_tasks/test_update_queries.py deleted file mode 100644 index 27f0dd1294..0000000000 --- a/datascience/tests/test_pipeline/test_shared_tasks/test_update_queries.py +++ /dev/null @@ -1,50 +0,0 @@ - -import pandas as pd -import pytest -from src.pipeline.shared_tasks.update_queries import merge_hashes, select_ids_to_delete, select_ids_to_insert, select_ids_to_update - -@pytest.fixture -def local_hashes() -> pd.DataFrame: - return pd.DataFrame( - { - "id": [1, 2, 3, 4, 6], - "cacem_row_hash": [ - "cacem_row_hash_1", - "cacem_row_hash_2", - "cacem_row_hash_3", - "cacem_row_hash_4_new", - "cacem_row_hash_6", - ], - } - ) - -@pytest.fixture -def remote_hashes() -> pd.DataFrame: - return pd.DataFrame( - { - "id": [1, 2, 3, 4, 5], - "monitorenv_row_hash": [ - "cacem_row_hash_1", - "cacem_row_hash_2", - "cacem_row_hash_3", - "cacem_row_hash_4", - "cacem_row_hash_5", - ], - } - ) - -def test_select_ids_to_delete(remote_hashes, local_hashes): - hashes = merge_hashes.run(local_hashes, remote_hashes) - ids_to_delete = select_ids_to_delete.run(hashes) - assert ids_to_delete == {5} - - -def test_select_ids_to_update(remote_hashes, local_hashes): - hashes = merge_hashes.run(local_hashes, remote_hashes, "inner") - ids_to_update = select_ids_to_update.run(hashes) - assert ids_to_update == {4} - -def test_select_ids_to_insert(remote_hashes, local_hashes): - hashes = merge_hashes.run(local_hashes, remote_hashes) - ids_to_insert = select_ids_to_insert.run(hashes) - assert ids_to_insert == {6} \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_utils.py b/datascience/tests/test_pipeline/test_utils.py deleted file mode 100644 index a6f11ee290..0000000000 --- a/datascience/tests/test_pipeline/test_utils.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import shutil -import tempfile -import unittest -from pathlib import Path - -from shapely import MultiPolygon - -from src.pipeline.utils import move - - -class TestProcessingMethods(unittest.TestCase): - def test_move_file_into_existing_directory(self): - with tempfile.TemporaryDirectory() as tmpdir: - tmpdir = Path(tmpdir) - dest_dirpath = tmpdir / "destination/directory" - os.makedirs(dest_dirpath) - - # Create a test file in tmpdir - tmp_file_path = tmpdir / "test_file.txt" - with open(tmp_file_path, "w+") as f: - f.write("Test file.") - self.assertIn("test_file.txt", os.listdir(tmpdir)) - self.assertNotIn("test_file.txt", os.listdir(dest_dirpath)) - - # Move the test file and test - move(tmp_file_path, dest_dirpath=dest_dirpath) - self.assertNotIn("test_file.txt", os.listdir(tmpdir)) - self.assertIn("test_file.txt", os.listdir(dest_dirpath)) - with open(dest_dirpath / "test_file.txt", "r") as f: - self.assertEqual(f.read(), "Test file.") - - def test_move_file_into_non_existing_directory(self): - with tempfile.TemporaryDirectory() as tmpdir: - tmpdir = Path(tmpdir) - dest_dirpath = tmpdir / "destination/directory" - - # Create a test file in tmp_dir - tmp_file_path = tmpdir / "test_file.txt" - with open(tmp_file_path, "w+") as f: - f.write("Test file.") - self.assertIn("test_file.txt", os.listdir(tmpdir)) - - # Move the test file and test - move(tmp_file_path, dest_dirpath=dest_dirpath) - self.assertNotIn("test_file.txt", os.listdir(tmpdir)) - self.assertIn("test_file.txt", os.listdir(dest_dirpath)) - with open(dest_dirpath / "test_file.txt", "r") as f: - self.assertEqual(f.read(), "Test file.") - - def test_move_file_already_exists(self): - with tempfile.TemporaryDirectory() as tmpdir: - tmpdir = Path(tmpdir) - dest_dirpath = tmpdir / "destination/directory" - os.makedirs(dest_dirpath) - - # Create a test file in tmpdir and in dest_dir - tmp_file_path = tmpdir / "test_file.txt" - with open(tmp_file_path, "w+") as f: - f.write("New file.") - with open(dest_dirpath / "test_file.txt", "w+") as f: - f.write("Original file.") - - self.assertIn("test_file.txt", os.listdir(tmpdir)) - self.assertIn("test_file.txt", os.listdir(dest_dirpath)) - - # Move the test file and test - with self.assertRaises( - shutil.Error - ): # Raise an error by default... - move(tmp_file_path, dest_dirpath) - - move( - tmp_file_path, dest_dirpath, if_exists="replace" - ) # Unless specified - - self.assertNotIn("test_file.txt", os.listdir(tmpdir)) - self.assertIn("test_file.txt", os.listdir(dest_dirpath)) - - with open(dest_dirpath / "test_file.txt", "r") as f: - self.assertEqual(f.read(), "New file.") - - # Test if_exists argument - with self.assertRaises(ValueError): - move(tmp_file_path, dest_dirpath, if_exists="unexpected") - - -def make_square_multipolygon( - init_lon, - init_lat, - width, - height, -): - return MultiPolygon( - [ - ( - ( - (init_lon, init_lat), - (init_lon + width, init_lat), - (init_lon + width, init_lat + height), - (init_lon, init_lat + height), - ), - [], - ) - ] - ) diff --git a/docker-compose.yml b/docker-compose.yml index e3bd6cffec..31431739f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,8 +95,6 @@ services: volumes: - db-data:/var/lib/postgresql/data - "${MONITORENV_BACKUPS_FOLDER}:/opt/monitorenv_backups" - # TODO: this was in docker-compose.override.yml - - "${MONITORENV_DATA_FOLDER}:/opt/data" ports: - ${POSTGRES_PORT}:5432 healthcheck: diff --git a/infra/data-pipeline-prefect3/Install.md b/infra/data-pipeline-prefect3/Install.md new file mode 100644 index 0000000000..bfcd087192 --- /dev/null +++ b/infra/data-pipeline-prefect3/Install.md @@ -0,0 +1,65 @@ +### Worker install + +1. Create virtual environment + +``` +su - monitorenv_etl +pyenv update +pyenv install 3.13.9 +pyenv virtualenv 3.13.9 prefect_worker_venv +pyenv activate prefect_worker_venv +pip install prefect==3.6.4 prefect-docker==0.6.6 +``` + +2. Create worker daemon + +- Create daemon script : +``` +su - monitorenv_etl +touch /home/monitorenv_etl/prefectworker.sh +chmod +x /home/monitorenv_etl/prefectworker.sh +``` + +- Copy paste into it : +``` +#!/bin/bash +export PREFECT_UI_URL=http://prefect-3.csam.e2.rie.gouv.fr +export PREFECT_API_URL=http://prefect-3.csam.e2.rie.gouv.fr/api +export PREFECT_WORKER_QUERY_SECONDS=5 +export PREFECT_WORKER_PREFETCH_SECONDS=50 + +source /home/monitorenv_etl/.pyenv/versions/3.13.9/envs/prefect_worker_venv/bin/activate && \ +prefect worker start --pool monitorenv --type docker --with-healthcheck +``` + +- Create service file +``` +su - monitorenv_etl +touch /home/monitorenv_etl/prefectworker.service +``` +- Copy paste into it : +``` +[Unit] +Description=Prefect Worker + +[Service] +User=monitorenv_etl +ExecStart=/home/monitorenv_etl/prefectworker.sh +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +- Make it available to systemd : + +```systemctl link /home/monitorenv_etl/prefectworker.service``` + +- Enable and start the daemon + +``` +systemctl daemon-reload +systemctl enable prefectworker.service +systemctl start prefectworker.service +``` diff --git a/infra/data-pipeline/.prefect-agent.template b/infra/data-pipeline/.prefect-agent.template deleted file mode 100644 index d379f1cfbf..0000000000 --- a/infra/data-pipeline/.prefect-agent.template +++ /dev/null @@ -1 +0,0 @@ -PREFECT_SERVER_URL= \ No newline at end of file diff --git a/infra/data-pipeline/prefectdockeragent.service b/infra/data-pipeline/prefectdockeragent.service deleted file mode 100644 index 928c210cdf..0000000000 --- a/infra/data-pipeline/prefectdockeragent.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Prefect Docker Agent - -[Service] -Type=simple - -User= -ExecStart=/prefectdockeragent.sh -Restart=always -RestartSec=30 - -[Install] -WantedBy=multi-user.target diff --git a/infra/data-pipeline/prefectdockeragent.sh b/infra/data-pipeline/prefectdockeragent.sh deleted file mode 100755 index 084c85482e..0000000000 --- a/infra/data-pipeline/prefectdockeragent.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -source /bin/activate && \ -source ~/.prefect-agent && \ -prefect agent docker start --label monitorenv --api "${PREFECT_SERVER_URL}"; diff --git a/infra/data-pipeline/register-flows.sh b/infra/data-pipeline/register-flows.sh deleted file mode 100755 index b931bf78c4..0000000000 --- a/infra/data-pipeline/register-flows.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -docker run --rm -t --network=host --name monitorenv-pipeline-register-flows \ - -v "$(pwd)"/infra/configurations/prefect-agent/backend.toml:/home/monitorenv-pipeline/.prefect/backend.toml \ - -v "$(pwd)"/datascience/.env:/home/monitorenv-pipeline/datascience/.env \ - --env-file datascience/.env \ - -e MONITORENV_VERSION \ - -e VESSEL_FILES_GID="$(getent group monitorenv_etl | cut -d: -f3)" \ - ghcr.io/mtes-mct/monitorenv/monitorenv-pipeline:$MONITORENV_VERSION \ - python main.py diff --git a/infra/data-pipeline/run-container-bash.sh b/infra/data-pipeline/run-container-bash.sh deleted file mode 100755 index 2eae673f64..0000000000 --- a/infra/data-pipeline/run-container-bash.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -docker run -it --network=host --name monitorenv-pipeline-agent \ - -v "$(pwd)"/infra/configurations/prefect-agent/backend.toml:/home/monitorenv-pipeline/.prefect/backend.toml \ - -v "$(pwd)"/datascience/.env:/home/monitorenv-pipeline/datascience/.env \ - --env-file datascience/.env \ - -e MONITORENV_VERSION \ - ghcr.io/mtes-mct/monitorenv/monitorenv-pipeline:$MONITORENV_VERSION \ - /bin/bash diff --git a/infra/docker/datapipeline/Dockerfile b/infra/docker/datapipeline/Dockerfile deleted file mode 100644 index 67b4caef77..0000000000 --- a/infra/docker/datapipeline/Dockerfile +++ /dev/null @@ -1,58 +0,0 @@ -FROM python:3.10.12-slim-bullseye - -ENV TINI_VERSION=v0.19.0 -ENV USER="monitorenv-pipeline" -ENV VIRTUAL_ENV=/opt/venv - -# Add `tini` init -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -RUN chmod +x /tini - -# Create non root user -RUN useradd -m -r ${USER} && \ - chown ${USER} /home/${USER} -WORKDIR /home/${USER} - -# Install system dependencies -RUN apt-get update && apt-get install -y \ - libpq-dev \ - build-essential \ - alien \ - libaio1 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -# # Download and install Oracle Instant Client -RUN wget \ - https://download.oracle.com/otn_software/linux/instantclient/19800/oracle-instantclient19.8-basic-19.8.0.0.0-1.x86_64.rpm \ - && alien --scripts oracle-instantclient19.8-basic-19.8.0.0.0-1.x86_64.rpm \ - && dpkg -i oracle-instantclient19.8-basic_19.8.0.0.0-2_amd64.deb \ - && rm oracle-instantclient19.8-basic-19.8.0.0.0-1.x86_64.rpm \ - && rm oracle-instantclient19.8-basic_19.8.0.0.0-2_amd64.deb - -# Create and "activate" venv by prepending it to PATH then install python dependencies -ENV PATH="$VIRTUAL_ENV/bin:$PATH" - -# Install python dependencies -COPY datascience/requirements.txt /tmp/requirements.txt -RUN python3 -m venv $VIRTUAL_ENV && \ - pip install -U \ - pip \ - setuptools \ - wheel && \ - pip install -r /tmp/requirements.txt - -# Make library importable -ENV PYTHONPATH=/home/${USER} - -# Add source -COPY datascience/ ./datascience -RUN pip install -e ./datascience -COPY backend/src/main/resources/db/migration ./backend/src/main/resources/db/migration -RUN mkdir /home/${USER}/.prefect/ - -RUN chown -R ${USER} . -USER ${USER} -WORKDIR /home/${USER}/datascience -ENTRYPOINT ["/tini", "--"] -CMD ["python", "main.py"] diff --git a/pipeline/.pre-commit-config.yaml b/pipeline/.pre-commit-config.yaml index a0bc3580cd..e178e1dcbd 100644 --- a/pipeline/.pre-commit-config.yaml +++ b/pipeline/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/myint/autoflake - rev: v1.4 + rev: v2.3.1 hooks: - id: autoflake name: autoflake @@ -21,7 +21,7 @@ repos: additional_dependencies: ['click==8.0.4'] language_version: python3 - repo: https://github.com/pycqa/flake8 - rev: 3.8.4 + rev: 7.3.0 hooks: - id: flake8 args: # arguments to configure flake8 diff --git a/pipeline/poetry.lock b/pipeline/poetry.lock index 5ed774ed7f..5316fed6fe 100644 --- a/pipeline/poetry.lock +++ b/pipeline/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aiosqlite" @@ -233,6 +233,21 @@ files = [ {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] +[[package]] +name = "autoflake" +version = "2.3.1" +description = "Removes unused imports and unused variables" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840"}, + {file = "autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e"}, +] + +[package.dependencies] +pyflakes = ">=3.0.0" + [[package]] name = "beartype" version = "0.22.6" @@ -1021,6 +1036,23 @@ files = [ {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, ] +[[package]] +name = "flake8" +version = "7.3.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.14.0,<2.15.0" +pyflakes = ">=3.4.0,<3.5.0" + [[package]] name = "fsspec" version = "2025.10.0" @@ -2853,6 +2885,18 @@ files = [ beartype = ">=0.20.0" typing-extensions = ">=4.15.0" +[[package]] +name = "pycodestyle" +version = "2.14.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, +] + [[package]] name = "pycparser" version = "2.23" @@ -3096,6 +3140,18 @@ rich = ">=13.9.4" typer = ">=0.15.1" typing-extensions = ">=4.12.0" +[[package]] +name = "pyflakes" +version = "3.4.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, +] + [[package]] name = "pygments" version = "2.19.2" @@ -4760,4 +4816,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "3.13.9" -content-hash = "90d57db697bb16dd0014f8741f1ad3674a04d585c00fe2e24102bda6e58e5e33" +content-hash = "630d2661f884cc801c720cdeb046412c9046ea3b2231ca03a79c9be76f78da7f" diff --git a/pipeline/pyproject.toml b/pipeline/pyproject.toml index dae75f8b02..872dbed003 100644 --- a/pipeline/pyproject.toml +++ b/pipeline/pyproject.toml @@ -34,6 +34,8 @@ pylint = "^4.0.3" ## Automation and management pre-commit = "^4.5.0" ipython = "^9.7.0" +autoflake = "^2.3.1" +flake8 = "^7.3.0" [tool.poetry.scripts] cli = "bin.cli:cli" diff --git a/pipeline/src/queries/monitorfish/fao_areas.sql b/pipeline/src/queries/monitorfish/fao_areas.sql deleted file mode 100644 index bd31e69e00..0000000000 --- a/pipeline/src/queries/monitorfish/fao_areas.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - f_code, - wkb_geometry as geometry -FROM fao_areas \ No newline at end of file diff --git a/datascience/tests/test_analytics_views/test_analytics_controls_locations_view.py b/pipeline/tests/test_analytics_views/test_analytics_controls_locations_view.py similarity index 100% rename from datascience/tests/test_analytics_views/test_analytics_controls_locations_view.py rename to pipeline/tests/test_analytics_views/test_analytics_controls_locations_view.py