diff --git a/.gitignore b/.gitignore index b13fa4173..725b32d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ +.ruff_cache cover/ # Translations diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..4182b8ef1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Docker runner Pre-award Stores", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5692 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder:funding-service-pre-award-stores}", + "remoteRoot": "." + } + ], + "justMyCode": true + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6fbbf1ba8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, +} diff --git a/fund_store/.devcontainer/devcontainer.json b/fund_store/.devcontainer/devcontainer.json new file mode 100644 index 000000000..7d21227fd --- /dev/null +++ b/fund_store/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +{ + "dockerComposeFile": [ + "../docker-compose.yml" + ], + "service": "fund-store", + "workspaceFolder": "/fund-store", + "shutdownAction": "none", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.debugpy", + "ms-python.vscode-pylance", + "eamodio.gitlens", + "ms-python.flake8", + "ms-python.black-formatter" + ] + } + } +} diff --git a/fund_store/Dockerfile b/fund_store/Dockerfile new file mode 100644 index 000000000..176ca4b21 --- /dev/null +++ b/fund_store/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.10-bullseye + +WORKDIR /app + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Install the project's dependencies using the lockfile and settings +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project + +# Then, add the rest of the project source code and install it +# Installing separately from its dependencies allows optimal layer caching +COPY . . +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" +EXPOSE 8080 + +CMD ["gunicorn", "--worker-class", "uvicorn.workers.UvicornWorker", "wsgi:app", "-b", "0.0.0.0:8080"] diff --git a/fund_store/README.md b/fund_store/README.md new file mode 100644 index 000000000..13bc9a004 --- /dev/null +++ b/fund_store/README.md @@ -0,0 +1,109 @@ +# funding-service-design-fund-store + +[![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) +[![Code style : black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +This is the fund store for funding service design Access Funding. This service provides an API and associated model implementation for fund and round configuration data. + +[Developer setup guide](https://github.com/communitiesuk/funding-service-design-workflows/blob/main/readmes/python-repos-setup.md) + +This service depends on: +- A postgres database +- No other microservices + +# Data +## Local DB Setup +General instructions for local db development are available here: [Local database development](https://github.com/communitiesuk/funding-service-design-workflows/blob/main/readmes/python-repos-db-development.md) + +## DB Helper Scripts +This repository uses `invoke` to provide scripts for dropping and recreating the local database in [tasks.py](./tasks.py) + +### Running Locally + +To run locally, make sure `psql` client is installed on your machine(https://www.postgresql.org/download/) and set the environment variable `DATABASE_URL`, +```bash +# pragma: allowlist nextline secret +export DATABASE_URL=postgresql://postgres:password@127.0.0.1:5432/fund_store +``` + +### Running in-container +To run the tasks inside the docker container used by docker compose, first bash into the container: +```bash +docker exec -it $(docker ps -qf "name=fund-store") bash +``` +Then execute the required tasks using `inv` as below. + +Or to combine the two into one command: +```bash + docker exec -it $(docker ps -qf "name=fund-store") inv truncate-data +``` + +### Available scripts +The following commands are the same locally or in container + +### Recreate DB instance + + inv recreate-local-db + +this drops (if it exists) and recreates the DB + +### Truncate data + + inv truncate-data +) + +## Seeding Fund Data +To seed fund & round data to db for all funds and rounds, use the fund/round loaders scripts. + +If running against a local postgresql instance: +```bash + python -m scripts.load_all_fund_rounds +``` + +If running with the docker compose setup: + +```bash + docker exec -ti $(docker ps -qf "name=fund-store") python -m scripts.load_all_fund_rounds +``` + +Further details on the fund/round loader scripts, and how to load data for a specific fund or round can be found [here](https://dluhcdigital.atlassian.net/wiki/spaces/FS/pages/40337455/Adding+or+updating+fund+and+round+data) + +## Amending round dates +This script allows you to open/close rounds using their dates to test different functionality as needed. You can also use the keywords 'PAST', 'FUTURE' and 'UNCHANGED' to save typing dates. + +```bash +docker exec -ti $(docker ps -qf "name=fund-store") python -m scripts.amend_round_dates -q update-round-dates --round_id c603d114-5364-4474-a0c4-c41cbf4d3bbd --application_deadline "2023-03-30 12:00:00" + +docker exec -ti $(docker ps -qf "name=fund-store") python -m scripts.amend_round_dates -q update-round-dates -r COF_R3W3 -o "2022-10-04 12:00:00" -d "2022-12-14 11:59:00" -ad "2023-03-30 12:00:00" -as NONE + +docker exec -ti $(docker ps -qf "name=fund-store") python -m scripts.amend_round_dates -q update-round-dates -r COF_R3W3 -o PAST -d FUTURE +``` +For an interactive prompt where you can supply (or leave unchanged) all dates: +```bash +docker exec -ti $(docker ps -qf "name=fund-store") python -m scripts.amend_round_dates update-round-dates +``` +To reset the dates for a round to those in the fund loader config: +```bash +docker exec -ti $(docker ps -qf "name=fund-store") python -m scripts.amend_round_dates -q reset-round-dates -r COF_R4W1 +``` +And with an interactive prompt: +```bash +docker exec -ti $(docker ps -qf "name=fund-store") python -m scripts.amend_round_dates reset-round-dates +``` + +# Testing +[Testing in Python repos](https://github.com/communitiesuk/funding-service-design-workflows/blob/main/readmes/python-repos-db-development.md) + + +# IDE Setup +[Python IDE Setup](https://github.com/communitiesuk/funding-service-design-workflows/blob/main/readmes/python-repos-ide-setup.md) + + +# Builds and Deploys +Details on how our pipelines work and the release process is available [here](https://dluhcdigital.atlassian.net/wiki/spaces/FS/pages/73695505/How+do+we+deploy+our+code+to+prod) +## Paketo +Paketo is used to build the docker image which gets deployed to our test and production environments. Details available [here](https://github.com/communitiesuk/funding-service-design-workflows/blob/main/readmes/python-repos-paketo.md) +## Copilot +Copilot is used for infrastructure deployment. Instructions are available [here](https://github.com/communitiesuk/funding-service-design-workflows/blob/main/readmes/python-repos-copilot.md), with the following values for the fund store: +- service-name: fsd-fund-store +- image-name: funding-service-design-fund-store diff --git a/fund_store/api/routes.py b/fund_store/api/routes.py new file mode 100644 index 000000000..3fb2713cf --- /dev/null +++ b/fund_store/api/routes.py @@ -0,0 +1,403 @@ +import uuid +from datetime import datetime +from distutils.util import strtobool + +from flask import abort, current_app, jsonify, request +from fsd_utils.locale_selector.get_lang import get_lang + +from db import db +from db.models import Round +from db.models.event import EventType +from db.queries import create_event as create_event_in_db +from db.queries import ( + get_all_funds, + get_application_sections_for_round, + get_assessment_sections_for_round, + get_fund_by_id, + get_fund_by_short_name, + get_round_by_id, + get_round_by_short_name, + get_rounds_for_fund_by_id, + get_rounds_for_fund_by_short_name, +) +from db.queries import get_event as get_event_from_db +from db.queries import get_events as get_events_from_db +from db.queries import set_event_to_processed as set_event_to_processed_in_db +from db.schemas.event import EventSchema +from db.schemas.fund import FundSchema +from db.schemas.round import RoundSchema +from db.schemas.section import SECTION_SCHEMA_MAP + + +def is_valid_uuid(value): + try: + obj = uuid.UUID(value) + return str(obj) == value.lower() + except Exception: + return False + + +def is_valid_isoformat_datetime(datetime_str): + try: + datetime.fromisoformat(datetime_str) + except Exception: + return False + return True + + +def filter_fund_by_lang(fund_data, lang_key: str = "en"): + def filter_fund(data): + data["name"] = data["name_json"].get(lang_key) or data["name_json"]["en"] + data["title"] = data["title_json"].get(lang_key) or data["title_json"]["en"] + data["description"] = data["description_json"].get(lang_key) or data["description_json"]["en"] + return data + + if isinstance(fund_data, dict): + fund = filter_fund(fund_data) + elif isinstance(fund_data, list): + fund = [filter_fund(item) for item in fund_data] + else: + fund = fund_data + + return fund + + +def filter_round_by_lang(round_data, lang_key: str = "en"): + def filter_round(data): + data["title"] = data["title_json"].get(lang_key) or data["title_json"]["en"] + data["contact_us_banner"] = ( + data["contact_us_banner_json"].get(lang_key) or data["contact_us_banner_json"].get("en") + if data["contact_us_banner_json"] + else "" + ) + data["instructions"] = ( + data["instructions_json"].get(lang_key) or data["instructions_json"].get("en") + if data["instructions_json"] + else "" + ) + data["application_guidance"] = ( + data["application_guidance_json"].get(lang_key) or data["application_guidance_json"].get("en") + if data["application_guidance_json"] + else "" + ) + return data + + if isinstance(round_data, dict): + round = filter_round(round_data) + elif isinstance(round_data, list): + round = [filter_round(item) for item in round_data] + else: + round = round_data + + return round + + +def get_funds(): + language = request.args.get("language", "en").replace("?", "") + funds = get_all_funds() + + if funds: + serialiser = FundSchema() + return jsonify(filter_fund_by_lang(fund_data=serialiser.dump(funds, many=True), lang_key=language)) + current_app.logger.warning("No funds were found, please check this.") + return jsonify(funds) + + +def get_fund(fund_id): + language = request.args.get("language", "en").replace("?", "") + short_name_arg = request.args.get("use_short_name") + use_short_name = short_name_arg and strtobool(short_name_arg) + + if use_short_name: + fund = get_fund_by_short_name(fund_id) if fund_id else None + else: + fund = get_fund_by_id(fund_id) if is_valid_uuid(fund_id) else None + + if fund: + serialiser = FundSchema() + return jsonify(filter_fund_by_lang(fund_data=serialiser.dump(fund), lang_key=language)) + + abort(404) + + +def get_round_from_db(fund_id, round_id) -> Round: + short_name_arg = request.args.get("use_short_name") + use_short_name = short_name_arg and strtobool(short_name_arg) + + if use_short_name: + round = get_round_by_short_name(fund_id, round_id) + else: + round = get_round_by_id(fund_id, round_id) if is_valid_uuid(fund_id) and is_valid_uuid(round_id) else None + return round + + +def get_round(fund_id, round_id): + round = get_round_from_db(fund_id, round_id) + language = request.args.get("language", "en").replace("?", "") + if round: + serialiser = RoundSchema() + return filter_round_by_lang(round_data=serialiser.dump(round), lang_key=language) + + abort(404) + + +def get_eoi_deicision_schema_for_round(fund_id, round_id): + language = request.args.get("language", "en").replace("?", "").lower() + round = get_round_from_db(fund_id=fund_id, round_id=round_id) + if not round: + abort(404) + + if not round.eoi_decision_schema: + return {} + + return round.eoi_decision_schema.get(language) or {} + + +def get_rounds_for_fund(fund_id): + language = request.args.get("language", "en").replace("?", "") + short_name_arg = request.args.get("use_short_name") + use_short_name = short_name_arg and strtobool(short_name_arg) + + if use_short_name: + rounds = get_rounds_for_fund_by_short_name(fund_id) + else: + rounds = get_rounds_for_fund_by_id(fund_id) if is_valid_uuid(fund_id) else None + + if rounds: + serialiser = RoundSchema() + dumped = [serialiser.dump(r) for r in rounds] + return filter_round_by_lang(round_data=dumped, lang_key=language) + + abort(404) + + +def get_sections_for_round_application(fund_id, round_id): + language = request.args.get("language", "en").replace("?", "") + if is_valid_uuid(fund_id) and is_valid_uuid(round_id): + sections = get_application_sections_for_round(fund_id, round_id) + if sections: + section_schema = SECTION_SCHEMA_MAP.get(language) + serialiser = section_schema() + dumped = serialiser.dump(sections, many=True) + return dumped + abort(404) + + +def get_sections_for_round_assessment(fund_id, round_id): + language = request.args.get("language", "en").replace("?", "") + if is_valid_uuid(fund_id) and is_valid_uuid(round_id): + sections = get_assessment_sections_for_round(fund_id, round_id, get_lang()) + if sections: + section_schema = SECTION_SCHEMA_MAP.get(language) + serialiser = section_schema() + return serialiser.dump(sections, many=True) + + abort(404) + + +def create_event(): + args = request.get_json() + if "type" not in args: + abort(400, "Post body must contain event type field") + + if "activation_date" not in args or not is_valid_isoformat_datetime(args["activation_date"]): + abort(400, "Activation date must be in isoformat datetime") + + if "processed" in args and not (is_valid_isoformat_datetime(args["processed"])): + abort(400, "Processed field must be an isoformat datetime") + + if "round_id" in args and not is_valid_uuid(args["round_id"]): + abort(400, "Round ID must be a UUID") + + event = create_event_in_db( + type=args["type"], + activation_date=args["activation_date"], + round_id=args.get("round_id"), + processed=args.get("processed"), + ) + + if event: + serialiser = EventSchema() + return serialiser.dump(event), 201 + abort(500) + + +def get_events_for_round(fund_id, round_id): + if not is_valid_uuid(round_id): + abort(400, "One or more IDs is not of format UUID") + + only_unprocessed = request.args.get("only_unprocessed", False, type=lambda x: x.lower() == "true") + events = get_events_from_db(round_id=round_id, only_unprocessed=only_unprocessed) + if events: + serialiser = EventSchema() + return serialiser.dump(events, many=True) + abort(404) + + +def get_events_by_type(type): + if not any(type == event_type.value for event_type in EventType): + abort(400, "Event type not recognised") + only_unprocessed = request.args.get("only_unprocessed", False, type=lambda x: x.lower() == "true") + events = get_events_from_db(type=type, only_unprocessed=only_unprocessed) + if events: + serialiser = EventSchema() + return serialiser.dump(events, many=True) + abort(404) + + +# TODO: deprecate in favour of get_event_by_id +def get_event_for_round(fund_id, round_id, event_id): + if not is_valid_uuid(event_id) or not is_valid_uuid(round_id): + abort(400, "One or more IDs is not of format UUID") + + event = get_event_from_db(round_id=round_id, event_id=event_id) + if event: + serialiser = EventSchema() + return serialiser.dump(event) + + abort(404) + + +def get_event_by_id(event_id): + if not is_valid_uuid(event_id): + abort(400, "One or more IDs is not of format UUID") + event = get_event_from_db(event_id=event_id) + if event: + serialiser = EventSchema() + return serialiser.dump(event) + abort(404) + + +def set_round_event_to_processed(fund_id, round_id, event_id): + if not is_valid_uuid(event_id) or not is_valid_uuid(round_id): + abort(400, "One or more IDs is not of format UUID") + processed = request.args.get("processed", type=lambda x: x.lower() == "true") + event = set_event_to_processed_in_db(event_id=event_id, processed=processed) + if event: + serialiser = EventSchema() + return jsonify(serialiser.dump(event)) + abort(404) + + +def set_event_to_processed(event_id): + if not is_valid_uuid(event_id): + abort(400, "One or more IDs is not of format UUID") + processed = request.args.get("processed", type=lambda x: x.lower() == "true") + event = set_event_to_processed_in_db(event_id=event_id, processed=processed) + if event: + serialiser = EventSchema() + return jsonify(serialiser.dump(event)) + abort(404) + + +def get_available_flag_allocations(fund_id, round_id): + # TODO: Currently teams are hardcoded, move it to database implementation + from config.fund_loader_config.cof.cof_r2 import ( + COF_ROUND_2_WINDOW_2_ID, + COF_ROUND_2_WINDOW_3_ID, + ) + from config.fund_loader_config.cof.cof_r3 import ( + COF_FUND_ID, + COF_ROUND_3_WINDOW_1_ID, + COF_ROUND_3_WINDOW_2_ID, + COF_ROUND_3_WINDOW_3_ID, + ) + from config.fund_loader_config.cof.cof_r4 import COF_ROUND_4_WINDOW_1_ID + from config.fund_loader_config.cyp.cyp_r1 import CYP_FUND_ID, CYP_ROUND_1_ID + from config.fund_loader_config.digital_planning.dpi_r2 import ( + DPI_FUND_ID, + DPI_ROUND_2_ID, + ) + from config.fund_loader_config.night_shelter.ns_r2 import ( + NIGHT_SHELTER_FUND_ID, + NIGHT_SHELTER_ROUND_2_ID, + ) + + cof_teams = [ + {"key": "ASSESSOR", "value": "Assessor"}, + {"key": "COMMERCIAL_ASSESSOR", "value": "Commercial Assessor"}, + {"key": "LEAD_ASSESSOR", "value": "Lead Assessor"}, + {"key": "LEAD_COMMERCIAL_ASSESSOR", "value": "Lead Commercial Assessor"}, + {"key": "COF_POLICY", "value": "COF Policy"}, + ] + + nstf_teams = [ + {"key": "COMMERCIAL", "value": "Commercial"}, + {"key": "NSTF_TEAM", "value": "NSTF Team"}, + {"key": "HOUSING_JUSTICE", "value": "Housing Justice"}, + {"key": "HOMELESS_LINK", "value": "Homeless Link"}, + {"key": "RS_ADVISORS", "value": "RS Advisors"}, + ] + + cyp_teams = [ + {"key": "COMMERCIAL_ASSESSOR", "value": "Commercial Assessor"}, + {"key": "LEAD_ASSESSOR", "value": "Lead Assessor"}, + ] + + dpif_teams = [ + {"key": "ELIGIBILITY", "value": "Eligibility"}, + {"key": "MODERATION", "value": "Moderation"}, + {"key": "LEAD_ASSESSOR", "value": "Lead Assessor"}, + ] + if is_valid_uuid(fund_id) and is_valid_uuid(round_id): + if fund_id == COF_FUND_ID and round_id in COF_ROUND_2_WINDOW_2_ID: + return cof_teams + elif fund_id == COF_FUND_ID and round_id == COF_ROUND_2_WINDOW_3_ID: + return cof_teams + elif fund_id == COF_FUND_ID and round_id == COF_ROUND_3_WINDOW_1_ID: + return cof_teams + elif fund_id == COF_FUND_ID and round_id == COF_ROUND_3_WINDOW_2_ID: + return cof_teams + elif fund_id == COF_FUND_ID and round_id == COF_ROUND_3_WINDOW_3_ID: + return cof_teams + elif fund_id == COF_FUND_ID and round_id == COF_ROUND_4_WINDOW_1_ID: + return cof_teams + elif fund_id == NIGHT_SHELTER_FUND_ID and round_id == NIGHT_SHELTER_ROUND_2_ID: + return nstf_teams + elif fund_id == CYP_FUND_ID and round_id == CYP_ROUND_1_ID: + return cyp_teams + elif fund_id == DPI_FUND_ID and round_id == DPI_ROUND_2_ID: + return dpif_teams + abort(404) + + +def update_application_reminder_sent_status(round_id): + try: + status = request.args.get("status") + round_instance = Round.query.filter_by(id=round_id).first() if is_valid_uuid(round_id) else None + + if not round_instance: + return jsonify({"message": "Round ID not found"}), 404 + reminder_status = round_instance.application_reminder_sent + + if status.lower() == "true" and reminder_status is False: + round_instance.application_reminder_sent = True + db.session.commit() + ( + current_app.logger.info( + {f"application_reminder_sent status has been updated to True for round {round_id}"} + ), + 200, + ) + return ( + jsonify({"message": f"application_reminder_sent status has been updated to True for round {round_id}"}), + 200, + ) + else: + return ( + jsonify({"message": "application_reminder_sent Status should be True"}), + 400, + ) + + except Exception as e: + ( + current_app.logger.error( + "The application_reminder_sent status could not be updated {error}", + extra=dict(error=str(e)), + ), + 400, + ) + return ( + jsonify({"message": f"The application_reminder_sent status could not be updated for round_id {round_id}"}), + 400, + ) diff --git a/fund_store/app.py b/fund_store/app.py new file mode 100644 index 000000000..e4da1a9fb --- /dev/null +++ b/fund_store/app.py @@ -0,0 +1,56 @@ +import connexion +import psycopg2 +from connexion import FlaskApp +from flask import jsonify +from fsd_utils import init_sentry +from fsd_utils.healthchecks.checkers import DbChecker, FlaskRunningChecker +from fsd_utils.healthchecks.healthcheck import Healthcheck +from fsd_utils.logging import logging +from sqlalchemy_utils import Ltree + +from db.models import ( + Fund, # noqa + Round, # noqa + Section, # noqa +) +from openapi.utils import get_bundled_specs + + +def create_app() -> FlaskApp: + init_sentry() + connexion_app = connexion.App( + "Fund Store", + ) + + connexion_app.add_api( + get_bundled_specs("/openapi/api.yml"), + validate_responses=True, + ) + flask_app = connexion_app.app + flask_app.config.from_object("config.Config") + from db import db, migrate + + # Bind SQLAlchemy ORM to Flask app + db.init_app(flask_app) + # Bind Flask-Migrate db utilities to Flask app + migrate.init_app(flask_app, db, directory="db/migrations", render_as_batch=True) + # Enable mapping of ltree datatype for sections + psycopg2.extensions.register_adapter(Ltree, lambda ltree: psycopg2.extensions.QuotedString(str(ltree))) + + # Initialise logging + logging.init_app(flask_app) + + health = Healthcheck(flask_app) + health.add_check(FlaskRunningChecker()) + health.add_check(DbChecker(db)) + + @flask_app.errorhandler(404) + def not_found(error): + flask_app.logger.warning("requested URL was not found on the server") + return jsonify({"code": 404, "message": "Requested URL was not found on the server"}), 404 + + return connexion_app + + +app = create_app() +application = app.app diff --git a/fund_store/config/__init__.py b/fund_store/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fund_store/config/fund_loader_config/FAB/__init__.py b/fund_store/config/fund_loader_config/FAB/__init__.py new file mode 100644 index 000000000..52be7adcb --- /dev/null +++ b/fund_store/config/fund_loader_config/FAB/__init__.py @@ -0,0 +1,62 @@ +import ast +import os +from pathlib import Path + +""" +Goes through all the files in config/fund_loader_config/FAB and adds each round to FAB_FUND_ROUND_CONFIGS + +Each file in that directory needs to be python format as per the FAB exports, +containing one property called LOADER_CONFIG +See test_fab_round_config.py for example + +FAB_FUND_ROUND_CONFIGS example: +{ + "COF25":{ + "id": "xxx", + "short_name": "COF25" + "rounds":{ + "R1": { + "id": "yyy", + "fund_id": "xxx", + "short_name": "R1", + "sections_config": {} + } + } + } +} +""" + +FAB_FUND_ROUND_CONFIGS = {} + +this_dir = Path("config") / "fund_loader_config" / "FAB" + +for file in os.listdir(this_dir): + if file.startswith("__") or not file.endswith(".py"): + continue + + with open(this_dir / file, "r") as json_file: + + content = json_file.read() + if content.startswith("LOADER_CONFIG = "): + content = content.split("LOADER_CONFIG = ")[1] + elif content.startswith("LOADER_CONFIG="): + content = content.split("LOADER_CONFIG=")[1] + else: + raise ValueError(f"fund config file {file.title()} does not start with 'LOADER_CONFIG='") + loader_config = ast.literal_eval(content) + if not loader_config.get("fund_config", None): + print("No fund config found in the loader config.") + raise ValueError(f"No fund_config found in {file}") + if not loader_config.get("round_config", None): + print("No round config found in the loader config.") + raise ValueError(f"No round_config found in {file}") + fund_short_name = loader_config["fund_config"]["short_name"] + round_short_name = loader_config["round_config"]["short_name"] + FAB_FUND_ROUND_CONFIGS[fund_short_name] = loader_config["fund_config"] + if not FAB_FUND_ROUND_CONFIGS[fund_short_name].get("rounds", None): + FAB_FUND_ROUND_CONFIGS[fund_short_name]["rounds"] = {} + FAB_FUND_ROUND_CONFIGS[fund_short_name]["rounds"][round_short_name] = loader_config["round_config"] + FAB_FUND_ROUND_CONFIGS[fund_short_name]["rounds"][round_short_name]["sections_config"] = loader_config[ + "sections_config" + ] + FAB_FUND_ROUND_CONFIGS[fund_short_name]["rounds"][round_short_name]["base_path"] = loader_config["base_path"] diff --git a/fund_store/config/fund_loader_config/FAB/cof_25.py b/fund_store/config/fund_loader_config/FAB/cof_25.py new file mode 100644 index 000000000..a16fd53b3 --- /dev/null +++ b/fund_store/config/fund_loader_config/FAB/cof_25.py @@ -0,0 +1,280 @@ +LOADER_CONFIG = { + "sections_config": [ + { + "section_name": { + "en": "1. About your organisation", + "cy": "1. Ynglŷn â'ch sefydliad", + }, + "tree_path": "1036.1.1", + "requires_feedback": True, + }, + { + "section_name": { + "en": "1.1 Organisation information", + "cy": "1.1 Gwybodaeth am y sefydliad", + }, + "form_name_json": { + "en": "organisation-information-cof-25", + "cy": "gwybodaeth-am-y-sefydliad-cof-25", + }, + "tree_path": "1036.1.1.1", + }, + { + "section_name": { + "en": "1.2 Applicant information", + "cy": "1.2 Gwybodaeth am yr ymgeisydd", + }, + "form_name_json": { + "en": "applicant-information-cof-25", + "cy": "gwybodaeth-am-yr-ymgeisydd-cof-25", + }, + "tree_path": "1036.1.1.2", + }, + { + "section_name": { + "en": "2. About your project", + "cy": "2. Ynglŷn â'ch prosiect", + }, + "tree_path": "1036.1.2", + "requires_feedback": True, + }, + { + "section_name": { + "en": "2.1 Project information", + "cy": "2.1 Gwybodaeth am y prosiect", + }, + "form_name_json": { + "en": "project-information-cof-25", + "cy": "gwybodaeth-am-y-prosiect-cof-25", + }, + "tree_path": "1036.1.2.1", + }, + { + "section_name": { + "en": "2.2 Asset information", + "cy": "2.2 Gwybodaeth am yr ased", + }, + "form_name_json": { + "en": "asset-information-cof-25", + "cy": "gwybodaeth-am-yr-ased-cof-25", + }, + "tree_path": "1036.1.2.2", + }, + { + "section_name": {"en": "3. Strategic case", "cy": "3. Achos strategol"}, + "tree_path": "1036.1.3", + "requires_feedback": True, + "weighting": 53, + }, + { + "section_name": {"en": "3.1 Community use/significance", "cy": "3.1 Defnydd/arwyddocâd cymunedol"}, + "form_name_json": { + "en": "community-use-cof-25", + "cy": "defnydd-cymunedol-cof-25", + }, + "tree_path": "1036.1.3.1", + }, + { + "section_name": { + "en": "3.2 Community engagement", + "cy": "3.2 Ymgysylltu â'r gymuned", + }, + "form_name_json": { + "en": "community-engagement-cof-25", + "cy": "ymgysylltiad-cymunedol-cof-25", + }, + "tree_path": "1036.1.3.2", + }, + { + "section_name": {"en": "3.3 Local support", "cy": "3.3 Cefnogaeth leol"}, + "form_name_json": { + "en": "local-support-cof-25", + "cy": "cefnogaeth-leol-cof-25", + }, + "tree_path": "1036.1.3.3", + }, + { + "section_name": { + "en": "3.4 Community benefits", + "cy": "3.4 Buddion cymunedol", + }, + "form_name_json": { + "en": "community-benefits-cof-25", + "cy": "buddion-cymunedol-cof-25", + }, + "tree_path": "1036.1.3.4", + }, + { + "section_name": { + "en": "3.5 Environmental sustainability", + "cy": "3.5 Cynaliadwyedd amgylcheddol", + }, + "form_name_json": { + "en": "environmental-sustainability-cof-25", + "cy": "cynaliadwyedd-amgylcheddol-cof-25", + }, + "tree_path": "1036.1.3.5", + }, + { + "section_name": {"en": "4. Management case", "cy": "4. Achos rheoli"}, + "tree_path": "1036.1.4", + "weighting": 47, + "requires_feedback": True, + }, + { + "section_name": { + "en": "4.1 Funding required", + "cy": "4.1 Cyllid sydd ei angen", + }, + "form_name_json": { + "en": "funding-required-cof-25", + "cy": "cyllid-sydd-ei-angen-cof-25", + }, + "tree_path": "1036.1.4.1", + }, + { + "section_name": {"en": "4.2 Feasibility ", "cy": "4.2 Dichonoldeb"}, + "form_name_json": { + "en": "feasibility-cof-25", + "cy": "dichonoldeb-cof-25", + }, + "tree_path": "1036.1.4.2", + }, + { + "section_name": {"en": "4.3 Risk", "cy": "4.3 Risg"}, + "tree_path": "1036.1.4.3", + "form_name_json": {"en": "risk-cof-25", "cy": "risg-cof-25"}, + }, + { + "section_name": {"en": "4.4 Operational costs", "cy": "4.4 Costau gweithredol"}, + "form_name_json": { + "en": "operational-costs-cof-25", + "cy": "costau-gweithredol-cof-25", + }, + "tree_path": "1036.1.4.4", + }, + { + "section_name": { + "en": "4.5 Skills and resources", + "cy": "4.5 Sgiliau ac adnoddau", + }, + "form_name_json": { + "en": "skills-and-resources-cof-25", + "cy": "sgiliau-ac-adnoddau-cof-25", + }, + "tree_path": "1036.1.4.5", + }, + { + "section_name": { + "en": "4.6 Community representation", + "cy": "4.6 Cynrychiolaeth gymunedol", + }, + "form_name_json": { + "en": "community-representation-cof-25", + "cy": "cynrychiolaeth-gymunedol-cof-25", + }, + "tree_path": "1036.1.4.6", + }, + { + "section_name": { + "en": "4.7 Inclusiveness and integration", + "cy": "4.7 Cynhwysiant ac integreiddio", + }, + "form_name_json": { + "en": "inclusiveness-and-integration-cof-25", + "cy": "cynhwysiant-ac-integreiddio-cof-25", + }, + "tree_path": "1036.1.4.7", + }, + { + "section_name": { + "en": "4.8 Upload business plan", + "cy": "4.8 Lanlwythwch y cynllun busnes", + }, + "form_name_json": { + "en": "upload-business-plan-cof-25", + "cy": "lanlwythwch-y-cynllun-busnes-cof-25", + }, + "tree_path": "1036.1.4.8", + }, + { + "section_name": {"en": "5. Check declarations", "cy": "5. Gwirio datganiadau"}, + "tree_path": "1036.1.5", + "requires_feedback": None, + }, + { + "section_name": {"en": "5.1 Declarations", "cy": "5.1 Datganiadau"}, + "form_name_json": { + "en": "declarations-cof-25", + "cy": "cadarnhadau-terfynol-cof-25", + }, + "tree_path": "1036.1.5.1", + }, + ], + "fund_config": { + "id": "604450fe-65c0-4a2e-a4ba-30ccf256056b", + "short_name": "COF25", + "welsh_available": True, + "owner_organisation_name": "None", + "owner_organisation_shortname": "None", + "owner_organisation_logo_uri": "None", + "funding_type": "COMPETITIVE", + "name_json": {"en": "Community Ownership Fund 2025", "cy": "Y Cronfa Perchnogaeth Gymunedol 2025"}, + "title_json": {"en": "funding to save an asset in your community", "cy": "gyllid i achub ased yn eich cymuned"}, + "description_json": { + "en": "The Community Ownership Fund is a £150 million fund over 4 years to support community groups across England, Wales, Scotland and Northern Ireland to take ownership of assets which are at risk of being lost to the community.", + "cy": "The Community Ownership Fund is a £150 million fund over 4 years to support community groups across England, Wales, Scotland and Northern Ireland to take ownership of assets which are at risk of being lost to the community.", + }, + }, + "round_config": { + "id": "38914fbf-9c31-41be-8547-c24d997aaba2", + "fund_id": "604450fe-65c0-4a2e-a4ba-30ccf256056b", + "short_name": "R1", + "opens": "2024-10-30T11:59:00", + "assessment_start": "2025-01-01T09:00:00", + "deadline": "2024-12-11T14:00:00", + "application_reminder_sent": False, + "reminder_date": "2024-12-01T11:59:00", + "assessment_deadline": "2025-01-31T17:00:00", + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus/community-ownership-fund-2025-prospectus", + "privacy_notice": "https://www.gov.uk/government/organisations/ministry-of-housing-communities-local-government", + "reference_contact_page_over_email": True, + "contact_email": "COF@communities.gov.uk", + "contact_phone": "", + "contact_textphone": "", + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": "You must have received an invitation to apply. If we did not invite you, first express your interest in the fund.", + "cy": "Mae'n rhaid i chi fod wedi derbyn gwahoddiad i ymgeisio. Os na wnaethom eich gwahodd, mynegwch eich diddordeb yn y gronfa yn gyntaf.", + }, + "feedback_link": "https://www.gov.uk/government/organisations/ministry-of-housing-communities-local-government", + "project_name_field_id": "apGjFS", + "application_guidance_json": { + "en": "
You can preview the full list of application questions.
We'll also ask you to upload a business plan to support the answers you've given us in the management case section.
", + "cy": "Gallwch gael cip ymlaen llaw o restr lawn cwestiynau'r cais.
Byddwn hefyd yn gofyn i chi lanlwytho cynllun busnes i ategu'r atebion rydych wedi'u rhoi i ni yn yr adran achos rheoli.
", + }, + "guidance_url": "https://www.gov.uk/government/organisations/ministry-of-housing-communities-local-government", + "all_uploaded_documents_section_available": False, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": True, + "is_expression_of_interest": False, + "eoi_decision_schema": None, + "feedback_survey_config": { + "has_feedback_survey": True, + "has_section_feedback": True, + "has_research_survey": True, + "is_feedback_survey_optional": False, + "is_research_survey_optional": True, + "is_section_feedback_optional": False, + }, + "eligibility_config": {"has_eligibility": False}, + "title_json": {"en": "Round 1", "cy": None}, + "contact_us_banner_json": { + "en": '\r\n Visit the My Community website\r\n for information and guidance on applying to Community Ownership Fund 2025.\r\n Fill out the enquiry form\r\n to request advice from My Community.\r\n
\r\n\r\n We cannot provide direct support to applicants outside of this service.\r\n
\r\n\r\n Contact the Ministry of Housing, Communities and Local Government funding team if you need\r\n help with accessing or submitting an application form.\r\n
\r\n', + "cy": '\r\n Ewch i wefan My Community\r\n i gael gwybodaeth ac arweiniad ar wneud cais i\'r Gronfa Perchnogaeth Gymunedol 2025.\r\n Llenwch y ffurflen ymholiad\r\n i ofyn am gyngor gan My Community.\r\n
\r\n\r\n Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i\'r gwasanaeth hwn.\r\n
\r\n\r\n Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno.\r\n
', + }, + }, + "base_path": 1036, +} diff --git a/fund_store/config/fund_loader_config/FAB/cof_25_eoi.py b/fund_store/config/fund_loader_config/FAB/cof_25_eoi.py new file mode 100644 index 000000000..93306cdb9 --- /dev/null +++ b/fund_store/config/fund_loader_config/FAB/cof_25_eoi.py @@ -0,0 +1,319 @@ +LOADER_CONFIG = { + "sections_config": [ + { + "section_name": { + "en": "1. Expression of interest", + "cy": "1. Mynegi diddordeb", + }, + "tree_path": "1037.1.1", + "requires_feedback": True, + }, + { + "section_name": { + "en": "1.1 Organisation details", + "cy": "1.1 Manylion y sefydliad", + }, + "tree_path": "1037.1.1.1", + "form_name_json": { + "en": "organisation-details-25", + "cy": "manylion-y-sefydliad-25", + }, + }, + { + "section_name": { + "en": "1.2 About your asset", + "cy": "1.2 Ynglŷn â'ch ased", + }, + "tree_path": "1037.1.1.2", + "form_name_json": { + "en": "about-your-asset-25", + "cy": "ynglyn-ach-ased-25", + }, + }, + { + "section_name": { + "en": "1.3 Your funding request", + "cy": "1.3 Eich cais am gyllid", + }, + "tree_path": "1037.1.1.3", + "form_name_json": { + "en": "your-funding-request-25", + "cy": "eich-cais-am-gyllid-25", + }, + }, + { + "section_name": { + "en": "1.4 Development support provider (not scored)", + "cy": "1.4 Darparwr cymorth datblygu (heb ei sgorio)", + }, + "tree_path": "1037.1.1.4", + "form_name_json": { + "en": "development-support-provider-25", + "cy": "darparwr-cymorth-datblygu-25", + }, + }, + { + "section_name": { + "en": "1.5 Declaration", + "cy": "1.5 Datganiad", + }, + "tree_path": "1037.1.1.5", + "form_name_json": { + "en": "declaration-25", + "cy": "datganiad-25", + }, + }, + ], + "fund_config": { + "id": "4db6072c-4657-458d-9f57-9ca59638317b", + "short_name": "COF25-EOI", + "welsh_available": True, + "owner_organisation_name": "None", + "owner_organisation_shortname": "None", + "owner_organisation_logo_uri": "None", + "funding_type": "EOI", + "name_json": {"en": "Community Ownership Fund 2025", "cy": "Y Cronfa Perchnogaeth Gymunedol 2025"}, + "title_json": { + "en": "expression of interest in applying for the Community Ownership Fund 2025", + "cy": "Gwneud cais am ddatganiad o ddiddordeb mewn gwneud cais i'r Gronfa Perchnogaeth Gymunedol 2025", + }, + "description_json": { + "en": "The Community Ownership Fund is a £150 million fund over 4 years to support community groups across England, Wales, Scotland and Northern Ireland to take ownership of assets which are at risk of being lost to the community.", + "cy": "The Community Ownership Fund is a £150 million fund over 4 years to support community groups across England, Wales, Scotland and Northern Ireland to take ownership of assets which are at risk of being lost to the community.", + }, + }, + "round_config": { + "id": "9104d809-0fb0-4144-b514-55e81cc2b6fa", + "fund_id": "4db6072c-4657-458d-9f57-9ca59638317b", + "short_name": "R1", + "opens": "2024-10-30T11:59:00", + "assessment_start": "2024-10-30T09:00:00", + "deadline": "2024-12-11T14:00:00", + "application_reminder_sent": False, + "reminder_date": "2024-12-01T11:59:00", + "assessment_deadline": "2025-01-31T17:00:00", + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus/community-ownership-fund-2025-prospectus", + "privacy_notice": "https://www.gov.uk/government/publications/community-ownership-fund-privacy-notice/community-ownership-fund-privacy-notice", + "reference_contact_page_over_email": True, + "contact_email": "COF@communities.gov.uk", + "contact_phone": "", + "contact_textphone": "", + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": "You must complete this Expression of Interest (EOI) form if you are interested in applying for the Community Ownership Fund 2025 (COF25).", + "cy": "Mae'n rhaid i chi gwblhau'r ffurflen Datganiad o Ddiddordeb hon os oes diddordeb gennych mewn gwneud cais i'r Gronfa Perchnogaeth Gymunedol 2025.", + }, + "feedback_link": "https://www.gov.uk/government/organisations/ministry-of-housing-communities-local-government", + "project_name_field_id": "SMRWjl", + "application_guidance_json": { + "en": "You can preview the full list of application questions.
", + "cy": "Gallwch gael rhagolwg o'r rhestr lawn o gwestiynau yn y cais.
", + }, + "guidance_url": "https://www.gov.uk/government/organisations/ministry-of-housing-communities-local-government", + "all_uploaded_documents_section_available": False, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": True, + "eoi_decision_schema": { + "en": { + "BykoQQ": [ + { + "answerValue": ["Not sure"], + "caveat": "Make progress in securing match funding: COF will contribute up to 80% of the capital costs you require, and you must raise at least 20% from other sources.You do not need to have secured all your match funding by the time you apply, but we will ask you to set out your total costs, funding already secured, and plans to raise any additional funding.You must use COF funding within 12 months, so you must be able to show that you've made good progress to secure the remaining match funding. This is so that we're confident you can draw down this funding within this timeframe.", + "result": 1, + } + ], + "NcQSbU": [{"answerValue": True, "caveat": None, "result": 2}], + "UORyaF": [ + { + "answerValue": "Not sure", + "caveat": "Get planning permission, if needed: When you apply, you must be able to show that you have secured or have made good progress in securing planning permission, if needed (and building warrants, if required). This is so that we're confident that COF funding will be used within the 12 month timeframe.", + "result": 1, + } + ], + "XuAyrs": [ + { + "answerValue": "Yes, a town, parish or community council", + "caveat": "Understand the rules on acquiring assets from town, parish or community councils: We cannot fund you to acquire a publicly owned asset if this involves transferring responsibility for delivering statutory services (services paid for by tax payers) from the public authority to your organisation. You should only apply to acquire an asset from a town, parish or community council if you do not plan to deliver statutory services.", + "result": 1, + }, + { + "answerValue": "Yes, another type of public authority", + "caveat": "Understand the rules on acquiring public sector assets: COF funding can only be used for renovation and refurbishment costs once a publicly owned asset has been transferred to you. We cannot fund capital receipts, unless the costs incurred in transferring the asset to you are nominal (very small and far below the real value).In your application, you should show that you are not asking COF to fund a capital receipt to a public authority (for example, by sharing a letter confirming the authority is willing/has already agreed a long-term lease and no capital receipt is involved).We also cannot fund you to acquire a publicly owned asset if this involves transferring responsibility for delivering statutory services (services paid for by tax payers) from the public authority to your organisation", + "result": 1, + }, + ], + "eEaDGz": [{"answerValue": False, "caveat": None, "result": 2}], + "eOWKoO": [{"answerValue": False, "caveat": None, "result": 2}], + "fBhSNc": [{"answerValue": False, "caveat": None, "result": 2}], + "fZAMFv": [{"caveat": None, "compareValue": 2000000, "operator": ">", "result": 2}], + "foQgiy": [{"answerValue": False, "caveat": None, "result": 2}], + "jICagT": [ + { + "answerValue": "Not yet started", + "caveat": "Get planning permission: When you apply, you must be able to show that you have secured or have made good progress in securing planning permission (and building warrants, if required). This is so that we're confident that COF funding will be used within the 12 month timeframe.", + "result": 1, + }, + { + "answerValue": "Early stage", + "caveat": "Get planning permission: When you apply, you must be able to show that you have secured or have made good progress in securing planning permission (and building warrants, if required). This is so that we're confident that COF funding will be used within the 12 month timeframe.", + "result": 1, + }, + ], + "kWRuac": [ + { + "answerValue": "Not yet approached any funders", + "caveat": "Make progress in securing match funding: COF will contribute up to 80% of the capital costs you require, and you must raise at least 20% from other sources. You do not need to have secured all your match funding by the time you apply, but we will ask you to set out your total costs, funding already secured, and plans to raise any additional funding. You must use COF funding within 12 months, so you must be able to show that you've made good progress to secure the remaining match funding. This is so that we're confident you can draw down this funding within this timeframe.", + "result": 1, + }, + { + "answerValue": "Approached some funders but not yet secured", + "caveat": "Make progress in securing match funding: COF will contribute up to 80% of the capital costs you require, and you must raise at least 20% from other sources. You do not need to have secured all your match funding by the time you apply, but we will ask you to set out your total costs, funding already secured, and plans to raise any additional funding. You must use COF funding within 12 months, so you must be able to show that you've made good progress to secure the remaining match funding. This is so that we're confident you can draw down this funding within this timeframe.", + "result": 1, + }, + { + "answerValue": "Approached all funders but not yet secured", + "caveat": "Make progress in securing match funding: COF will contribute up to 80% of the capital costs you require, and you must raise at least 20% from other sources. You do not need to have secured all your match funding by the time you apply, but we will ask you to set out your total costs, funding already secured, and plans to raise any additional funding. You must use COF funding within 12 months, so you must be able to show that you've made good progress to secure the remaining match funding. This is so that we're confident you can draw down this funding within this timeframe.", + "result": 1, + }, + { + "answerValue": "Secured some match funding", + "caveat": "Make progress in securing match funding: COF will contribute up to 80% of the capital costs you require, and you must raise at least 20% from other sources. You do not need to have secured all your match funding by the time you apply, but we will ask you to set out your total costs, funding already secured, and plans to raise any additional funding. You must use COF funding within 12 months, so you must be able to show that you've made good progress to secure the remaining match funding. This is so that we're confident you can draw down this funding within this timeframe.", + "result": 1, + }, + ], + "lLQmNb": [{"answerValue": False, "caveat": None, "result": 2}], + "oblxxv": [ + { + "answerValue": False, + "caveat": "Consider requesting revenue funding: We encourage all organisations to apply for revenue funding to help cover the initial running costs of your project. When you apply, you'll need to show us how you plan to use any revenue funding. See [Section 9 of the COF prospectus for more guidance](https://www.gov.uk/government/publications/community-ownership-fund-prospectus/community-ownership-fund-2025-prospectus#funding-available).", + "result": 1, + } + ], + "uYiLsv": [ + { + "answerValue": "not-yet-incorporated", + "caveat": "Incorporate your organisation: You must have incorporated your organisation by the time you submit a full application. If you remain unincorporated, your application will be ineligible.", + "result": 1, + } + ], + "yZxdeJ": [ + { + "answerValue": True, + "caveat": "Understand the rules on housing: We will not provide funding if your project's main purpose is to purchase or develop housing assets, including social housing. However, you can include housing elements in your project where these are only a small part of supporting the overall financial sustainability of the asset in community ownership.", + "result": 1, + } + ], + "zurxox": [{"answerValue": False, "caveat": None, "result": 2}], + }, + "cy": { + "uYiLsv": [ + { + "answerValue": "Ddim yn gorfforedig eto", + "result": 1, + "caveat": "Dylech gorffori eich sefydliad: Mae'n rhaid eich bod wedi corffori eich sefydliad erbyn eich bod yn cyflwyno cais llawn. Os byddwch yn anghorfforedig o hyd, ni fydd eich cais yn gymwys.", + } + ], + "NcQSbU": [{"answerValue": True, "result": 2, "caveat": None}], + "eEaDGz": [{"answerValue": False, "result": 2, "caveat": None}], + "zurxox": [{"answerValue": False, "result": 2, "caveat": None}], + "lLQmNb": [{"answerValue": False, "result": 2, "caveat": None}], + "fBhSNc": [{"answerValue": False, "result": 2, "caveat": None}], + "XuAyrs": [ + { + "answerValue": "Ydy, cyngor tref, plwyf neu gymuned", + "result": 1, + "caveat": "Dylech ddeall y rheolau yngl\\u0177n \\u00e2 chaffael asedau gan gynghorau tref, plwyf neu gymuned: Ni allwn eich ariannu i gaffael ased dan berchnogaeth gyhoeddus os yw'n golygu trosglwyddo cyfrifoldeb am ddarparu gwasanaethau statudol (gwasanaethau y mae trethdalwyr yn talu amdanynt) o'r awdurdod cyhoeddus i'ch sefydliad. Dim ond os nad ydych yn bwriadu darparu gwasanaethau statudol y dylech wneud cais i gaffael ased gan gyngor tref, plwyf neu gymuned.", + }, + { + "answerValue": "Ydy, math arall o awdurdod cyhoeddus", + "result": 1, + "caveat": "Dylech ddeall y rheolau yngl\\u0177n \\u00e2 chaffael asedau'r sector cyhoeddus: Dim ond ar \\u00f4l trosglwyddo ased sydd dan berchnogaeth gyhoeddus i chi y gellir defnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol ar gyfer costau adnewyddu ac ailwampio. Ni allwn ariannu derbyniad cyfalaf, oni bai bod y costau yr aed iddynt wrth drosglwyddo'r ased i chi yn nominal (bach iawn ac yn llawer is na'r gwerth gwirioneddol). Yn eich cais, dylech ddangos nad ydych yn gofyn i'r Gronfa Perchnogaeth Gymunedol ariannu derbyniad cyfalaf i awdurdod cyhoeddus (er enghraifft drwy rannu llythyr yn cadarnhau bod yr awdurdod yn fodlon ar/eisoes wedi cytuno i les hirdymor ac nad oes derbyniad cyfalaf yn gysylltiedig). Ni allwn ychwaith eich ariannu i gaffael ased dan berchnogaeth gyhoeddus os yw'n golygu trosglwyddo cyfrifoldeb am ddarparu gwasanaethau statudol (gwasanaethau y mae trethdalwyr yn talu amdanynt) o'r awdurdod cyhoeddus i'ch sefydliad.", + }, + ], + "foQgiy": [{"answerValue": False, "result": 2, "caveat": None}], + "BykoQQ": [ + { + "answerValue": ["none"], + "result": 1, + "caveat": "Gwneud cynnydd i sicrhau arian cyfatebol: Bydd y Gronfa Perchnogaeth Gymunedol yn cyfrannu hyd at 80% o'r costau cyfalaf sydd eu hangen arnoch, ac mae'n rhaid i chi godi o leiaf 20% o ffynonellau eraill. Nid oes angen i chi fod wedi sicrhau eich holl arian cyfatebol erbyn i chi wneud cais, ond byddwn yn gofyn i chi nodi cyfanswm eich costau, y cyllid rydych eisoes wedi'i sicrhau, a chynlluniau i godi unrhyw gyllid ychwanegol. Mae'n rhaid i chi ddefnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol o fewn 12 mis, felly mae'n rhaid i chi allu dangos eich bod wedi gwneud cynnydd da i sicrhau'r arian cyfatebol sy'n weddill. Diben hyn yw rhoi'r hyder i ni y gallwch ddefnyddio'r cyllid hwn o fewn yr amserlen hon.", + } + ], + "eOWKoO": [{"answerValue": False, "result": 2, "caveat": None}], + "oblxxv": [ + { + "answerValue": False, + "result": 1, + "caveat": "Ystyriwch wneud cais am gyllid refeniw: Rydym yn annog pob sefydliad i wneud cais am gyllid refeniw er mwyn helpu i dalu costau rhedeg cychwynnol eich prosiect. Pan fyddwch yn gwneud cais, bydd angen i chi ddangos i ni sut rydych yn bwriadu defnyddio unrhyw gyllid refeniw. [Gweler Adran 9 o brosbectws y Gronfa Perchnogaeth Gymunedol am ragor o ganllawiau.](https://www.gov.uk/government/publications/community-ownership-fund-prospectus/community-ownership-fund-prospectus--3#funding-available)", + } + ], + "kWRuac": [ + { + "answerValue": "Heb gysylltu ag unrhyw gyllidwyr eto", + "result": 1, + "caveat": "Gwneud cynnydd i sicrhau arian cyfatebol: Bydd y Gronfa Perchnogaeth Gymunedol yn cyfrannu hyd at 80% o'r costau cyfalaf sydd eu hangen arnoch, ac mae'n rhaid i chi godi o leiaf 20% o ffynonellau eraill. Nid oes angen i chi fod wedi sicrhau eich holl arian cyfatebol erbyn i chi wneud cais, ond byddwn yn gofyn i chi nodi cyfanswm eich costau, y cyllid rydych eisoes wedi'i sicrhau, a chynlluniau i godi unrhyw gyllid ychwanegol. Mae'n rhaid i chi ddefnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol o fewn 12 mis, felly mae'n rhaid i chi allu dangos eich bod wedi gwneud cynnydd da i sicrhau'r arian cyfatebol sy'n weddill. Diben hyn yw rhoi'r hyder i ni y gallwch ddefnyddio'r cyllid hwn o fewn yr amserlen hon.", + }, + { + "answerValue": "Wedi cysylltu \\u00e2 rhai cyllidwyr ond heb sicrhau cyllid eto", + "result": 1, + "caveat": "Gwneud cynnydd i sicrhau arian cyfatebol: Bydd y Gronfa Perchnogaeth Gymunedol yn cyfrannu hyd at 80% o'r costau cyfalaf sydd eu hangen arnoch, ac mae'n rhaid i chi godi o leiaf 20% o ffynonellau eraill. Nid oes angen i chi fod wedi sicrhau eich holl arian cyfatebol erbyn i chi wneud cais, ond byddwn yn gofyn i chi nodi cyfanswm eich costau, y cyllid rydych eisoes wedi'i sicrhau, a chynlluniau i godi unrhyw gyllid ychwanegol. Mae'n rhaid i chi ddefnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol o fewn 12 mis, felly mae'n rhaid i chi allu dangos eich bod wedi gwneud cynnydd da i sicrhau'r arian cyfatebol sy'n weddill. Diben hyn yw rhoi'r hyder i ni y gallwch ddefnyddio'r cyllid hwn o fewn yr amserlen hon.", + }, + { + "answerValue": "Wedi cysylltu \\u00e2'r holl gyllidwyr ond heb sicrhau cyllid eto", + "result": 1, + "caveat": "Gwneud cynnydd i sicrhau arian cyfatebol: Bydd y Gronfa Perchnogaeth Gymunedol yn cyfrannu hyd at 80% o'r costau cyfalaf sydd eu hangen arnoch, ac mae'n rhaid i chi godi o leiaf 20% o ffynonellau eraill. Nid oes angen i chi fod wedi sicrhau eich holl arian cyfatebol erbyn i chi wneud cais, ond byddwn yn gofyn i chi nodi cyfanswm eich costau, y cyllid rydych eisoes wedi'i sicrhau, a chynlluniau i godi unrhyw gyllid ychwanegol. Mae'n rhaid i chi ddefnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol o fewn 12 mis, felly mae'n rhaid i chi allu dangos eich bod wedi gwneud cynnydd da i sicrhau'r arian cyfatebol sy'n weddill. Diben hyn yw rhoi'r hyder i ni y gallwch ddefnyddio'r cyllid hwn o fewn yr amserlen hon.", + }, + { + "answerValue": "Wedi sicrhau rhywfaint o arian cyfatebol", + "result": 1, + "caveat": "Gwneud cynnydd i sicrhau arian cyfatebol: Bydd y Gronfa Perchnogaeth Gymunedol yn cyfrannu hyd at 80% o'r costau cyfalaf sydd eu hangen arnoch, ac mae'n rhaid i chi godi o leiaf 20% o ffynonellau eraill. Nid oes angen i chi fod wedi sicrhau eich holl arian cyfatebol erbyn i chi wneud cais, ond byddwn yn gofyn i chi nodi cyfanswm eich costau, y cyllid rydych eisoes wedi'i sicrhau, a chynlluniau i godi unrhyw gyllid ychwanegol. Mae'n rhaid i chi ddefnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol o fewn 12 mis, felly mae'n rhaid i chi allu dangos eich bod wedi gwneud cynnydd da i sicrhau'r arian cyfatebol sy'n weddill. Diben hyn yw rhoi'r hyder i ni y gallwch ddefnyddio'r cyllid hwn o fewn yr amserlen hon.", + }, + ], + "yZxdeJ": [ + { + "answerValue": True, + "result": 1, + "caveat": "Dylech ddeall y rheolau ynglyn \\u00e2 thai: Ni fyddwn yn darparu cyllid os mai prif ddiben eich prosiect yw prynu neu ddatblygu asedau tai, gan gynnwys tai cymdeithasol. Fodd bynnag, gallwch gynnwys elfennau tai yn eich prosiect os mai dim ond rhan fach o gefnogi cynaliadwyedd ariannol gyffredinol yr ased dan berchnogaeth gymunedol yw'r rhain.", + } + ], + "UORyaF": [ + { + "answerValue": "Ddim yn si\\u0175r", + "result": 1, + "caveat": "Sicrhewch ganiat\\u00e2d cynllunio, os oes angen: Pan fyddwch yn gwneud cais, rhaid i chi allu dangos eich bod wedi sicrhau caniat\\u00e2d cynllunio os oes angen (a gwarantau adeiladu, os oes angen), neu'ch bod wedi gwneud cynnydd da yn hyn o beth. Diben hyn yw rhoi'r hyder i ni y caiff cyllid o'r Gronfa Perchnogaeth Gymunedol ei ddefnyddio o fewn y cyfnod o 12 mis.", + } + ], + "jICagT": [ + { + "answerValue": "Heb ddechrau eto", + "result": 1, + "caveat": "Sicrhewch ganiat\\u00e2d cynllunio: Pan fyddwch yn gwneud cais, rhaid i chi allu dangos eich bod wedi sicrhau caniat\\u00e2d cynllunio (a gwarantau adeiladu, os oes angen), neu'ch bod wedi gwneud cynnydd da yn hyn o beth. Diben hyn yw rhoi'r hyder i ni y caiff cyllid o'r Gronfa Perchnogaeth Gymunedol ei ddefnyddio o fewn y cyfnod o 12 mis.", + }, + { + "answerValue": "Cam cynnar", + "result": 1, + "caveat": "Sicrhewch ganiat\\u00e2d cynllunio: Pan fyddwch yn gwneud cais, rhaid i chi allu dangos eich bod wedi sicrhau caniat\\u00e2d cynllunio (a gwarantau adeiladu, os oes angen), neu'ch bod wedi gwneud cynnydd da yn hyn o beth. Diben hyn yw rhoi'r hyder i ni y caiff cyllid o'r Gronfa Perchnogaeth Gymunedol ei ddefnyddio o fewn y cyfnod o 12 mis.", + }, + ], + "fZAMFv": [{"operator": ">", "compareValue": 2000000, "result": 2, "caveat": None}], + }, + }, + "feedback_survey_config": { + "has_feedback_survey": False, + "has_section_feedback": True, + "has_research_survey": True, + "is_feedback_survey_optional": False, + "is_research_survey_optional": True, + "is_section_feedback_optional": True, + }, + "eligibility_config": {"has_eligibility": False}, + "title_json": {"en": "Round 1", "cy": "Rownd 1"}, + "contact_us_banner_json": { + "en": '\r\n Visit the My Community website\r\n for information and guidance on applying to Community Ownership Fund 2025.\r\n Fill out the enquiry form\r\n to request advice from My Community.\r\n
\r\n\r\n We cannot provide direct support to applicants outside of this service.\r\n
\r\n\r\n Contact the Ministry of Housing, Communities & Local Government funding team if you need\r\n help with accessing or submitting an application form.\r\n
', + "cy": '\r\n Ewch i wefan My Community\r\n i gael gwybodaeth ac arweiniad ar wneud cais i\'r Gronfa Perchnogaeth Gymunedol 2025.\r\n Llenwch y ffurflen ymholiad\r\n i ofyn am gyngor gan My Community.\r\n
\r\n\r\n Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i\'r gwasanaeth hwn.\r\n
\r\n\r\n Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno.\r\n
', + }, + }, + "base_path": 1037, +} diff --git a/fund_store/config/fund_loader_config/cof/cof_r2.py b/fund_store/config/fund_loader_config/cof/cof_r2.py new file mode 100644 index 000000000..4b0539be3 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/cof_r2.py @@ -0,0 +1,427 @@ +# flake8: noqa +import textwrap +from datetime import datetime +from datetime import timezone + +from config.fund_loader_config.cof.shared import COF_APPLICATION_GUIDANCE +from config.fund_loader_config.cof.shared import fund_config +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + COF_R2_W2_BASE_PATH, +) + +COF_FUND_ID = fund_config["id"] +COF_ROUND_2_WINDOW_2_ID = "c603d114-5364-4474-a0c4-c41cbf4d3bbd" +COF_ROUND_2_WINDOW_3_ID = "5cf439bf-ef6f-431e-92c5-a1d90a4dd32f" +APPLICATION_BASE_PATH = ".".join([str(COF_R2_W2_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH = ".".join([str(COF_R2_W2_BASE_PATH), str(2)]) +COF_R2_OPENS_DATE = datetime(2022, 10, 4, 12, 0, 0, tzinfo=timezone.utc) # 2022-10-04 12:00:00 +COF_R2_DEADLINE_DATE = datetime(2022, 12, 14, 11, 59, 0, tzinfo=timezone.utc) # 2022-12-14 11:59:00 +COF_R2_ASSESSMENT_DEADLINE_DATE = datetime(2023, 3, 30, 12, 0, 0, tzinfo=timezone.utc) # 2023-03-30 12:00:00 + +rounds_config = [ + { + "id": COF_ROUND_2_WINDOW_2_ID, + "title_json": { + "en": "Round 2 Window 2", + "cy": "Round 2 Window 2", + }, # TODO: Provide welsh translation + "short_name": "R2W2", + "opens": COF_R2_OPENS_DATE, + "assessment_start": None, + "deadline": COF_R2_DEADLINE_DATE, + "application_reminder_sent": True, + "reminder_date": None, + "fund_id": "47aef2f5-3fcb-4d45-acb5-f0152b5f03c4", + "assessment_deadline": COF_R2_ASSESSMENT_DEADLINE_DATE, + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + ( + "https://www.gov.uk/government/publications/community-ownership-fund-privacy-notice/" + "community-ownership-fund-privacy-notice" + ), + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + "cy": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqN48ORk8WN5LlJITE3Swt-lURUNCR0dHMjgxWFZOMTMxQzlOTVIxVkQ0Sy4u" + ), + "project_name_field_id": "KAgrBz", + "application_guidance_json": COF_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://mhclg.sharepoint.com.mcas.ms/:w:/s/CommunityOwnershipFund" + "/Ecv3iM7U0AtKtyHnzRrQ9dsB0HdMPvHWqAoGn1WrWM7EMA?e=6QpdUT" + ), + "all_uploaded_documents_section_available": True, + "application_fields_download_available": False, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": False, + "has_section_feedback": False, + "is_feedback_survey_optional": True, + "is_section_feedback_optional": True, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + }, + { + "id": COF_ROUND_2_WINDOW_3_ID, + "title_json": { + "en": "Round 2 Window 3", + "cy": "Round 2 Window 3", + }, # TODO: Provide welsh translation + "short_name": "R2W3", + "opens": "2022-10-04 12:00:00", + "assessment_start": None, + "deadline": "2023-04-14 11:59:00", + "application_reminder_sent": True, + "reminder_date": None, + "fund_id": "47aef2f5-3fcb-4d45-acb5-f0152b5f03c4", + "assessment_deadline": "2023-05-17 12:00:00", + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + "https://www.gov.uk/government/publications/community-ownership-fund-privacy-notice/" + "community-ownership-fund-privacy-notice" + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + "cy": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElUREIySU9OWTU4R0RTNjhBUDE1Q1VYVFBEMi4u" + ), + "project_name_field_id": "KAgrBz", + "application_guidance_json": COF_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://mhclg.sharepoint.com.mcas.ms/:w:/s/CommunityOwnershipFund" + "/Ecv3iM7U0AtKtyHnzRrQ9dsB0HdMPvHWqAoGn1WrWM7EMA?e=6QpdUT" + ), + "all_uploaded_documents_section_available": True, + "application_fields_download_available": False, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": False, + "has_section_feedback": False, + "is_feedback_survey_optional": True, + "is_section_feedback_optional": True, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + }, +] + +cof_r2_sections = [ + { + "section_name": { + "en": "1. About your organisation", + "cy": "1. Ynglŷn â'ch sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.1", + }, + { + "section_name": { + "en": "Organisation Information", + "cy": "Gwybodaeth am y sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.1.1", + "form_name_json": { + "en": "organisation-information", + "cy": "gwybodaeth-am-y-sefydliad", + }, + }, + { + "section_name": { + "en": "Applicant Information", + "cy": "Gwybodaeth am yr ymgeisydd", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.1.2", + "form_name_json": { + "en": "applicant-information", + "cy": "gwybodaeth-am-y-ymgeisydd", + }, + }, + { + "section_name": { + "en": "2. About your project", + "cy": "2. Ynglŷn â'ch prosiect", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.2", + }, + { + "section_name": {"en": "Project Information", "cy": "Gwybodaeth am y prosiect"}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.1", + "form_name_json": { + "en": "project-information", + "cy": "gwybodaeth-am-y-prosiect", + }, + }, + { + "section_name": {"en": "Asset Information", "cy": "Gwybodaeth am yr ased"}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.2", + "form_name_json": {"en": "asset-information", "cy": "gwybodaeth-am-yr-ased"}, + }, + { + "section_name": {"en": "3. Strategic case", "cy": "3. Achos strategol"}, + "tree_path": f"{APPLICATION_BASE_PATH}.3", + "weighting": 30, + }, + { + "section_name": {"en": "Community Use", "cy": "Defnydd Cymunedol"}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.1", + "form_name_json": {"en": "community-use", "cy": "defnydd-cymunedol"}, + }, + { + "section_name": {"en": "Community Engagement", "cy": "Ymgysylltu â'r gymuned"}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.2", + "form_name_json": { + "en": "community-engagement", + "cy": "ymgysylltu-a'r-gymuned", + }, + }, + { + "section_name": {"en": "Local Support", "cy": "Cefnogaeth leol"}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.3", + "form_name_json": {"en": "local-support", "cy": "cefnogaeth-leol"}, + }, + { + "section_name": { + "en": "Environmental Sustainability", + "cy": "Cynaliadwyedd amgylcheddol", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.3.4", + "form_name_json": { + "en": "environmental-sustainability", + "cy": "cynaliadwyedd-amgylcheddol", + }, + }, + { + "section_name": {"en": "4. Management case", "cy": "4. Achos rheoli"}, + "tree_path": f"{APPLICATION_BASE_PATH}.4", + "weighting": 30, + }, + { + "section_name": {"en": "Funding Required", "cy": "Cyllid sydd ei angen"}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.1", + "form_name_json": {"en": "funding-required", "cy": "cyllid-sydd-ei-angen"}, + }, + { + "section_name": {"en": "Feasibility", "cy": "Dichonoldeb"}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.2", + "form_name_json": {"en": "feasibility", "cy": "cynhwysiant-ac-integreiddio"}, + }, + { + "section_name": {"en": "Risk", "cy": "Risg"}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.3", + "form_name_json": {"en": "risk", "cy": "risg"}, + }, + { + "section_name": {"en": "Project Costs", "cy": "Costau'r prosiect"}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.4", + "form_name_json": {"en": "project-costs", "cy": "costau'r-prosiect"}, + }, + { + "section_name": {"en": "Skills And Resources", "cy": "Sgiliau ac Adnoddau"}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.5", + "form_name_json": {"en": "skills-and-resources", "cy": "sgiliau-ac-adnoddau"}, + }, + { + "section_name": { + "en": "Community Representation", + "cy": "Cynrychiolaeth gymunedol", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.4.6", + "form_name_json": { + "en": "community-representation", + "cy": "cynrychiolaeth-gymunedol", + }, + }, + { + "section_name": { + "en": "Inclusiveness And Integration", + "cy": "Cynhwysiant ac Integreiddio", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.4.7", + "form_name_json": { + "en": "inclusiveness-and-integration", + "cy": "cynhwysiant-ac-integreiddio", + }, + }, + { + "section_name": { + "en": "Upload Business Plan", + "cy": "Lanlwythwch y cynllun busnes", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.4.8", + "form_name_json": { + "en": "upload-business-plan", + "cy": "lanlwythwch-y-cynllun-busnes", + }, + }, + { + "section_name": { + "en": "5. Potential to deliver community benefits", + "cy": "5. Potensial i gyflawni buddion cymunedol", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.5", + "weighting": 30, + }, + { + "section_name": {"en": "Community Benefits", "cy": "Buddion cymunedol"}, + "tree_path": f"{APPLICATION_BASE_PATH}.5.1", + "form_name_json": {"en": "community-benefits", "cy": "ymgysylltu-a'r-gymuned"}, + }, + { + "section_name": { + "en": "6. Added value to community", + "cy": "6. Gwerth ychwanegol i'r gymuned", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.6", + "weighting": 10, + }, + { + "section_name": {"en": "Value To The Community", "cy": "Gwerth i'r Gymuned"}, + "tree_path": f"{APPLICATION_BASE_PATH}.6.1", + "form_name_json": {"en": "value-to-the-community", "cy": "gwerth-i'r-gymuned"}, + }, + { + "section_name": { + "en": "7. Subsidy control / state aid", + "cy": "7. Rheoli cymorthdaliadau a chymorth gwladwriaethol", + }, + "tree_path": f"{APPLICATION_BASE_PATH}.7", + }, + { + "section_name": {"en": "Project Qualification", "cy": "Cymhwystra'r prosiect"}, + "tree_path": f"{APPLICATION_BASE_PATH}.7.1", + "form_name_json": { + "en": "project-qualification", + "cy": "cymhwystra'r-prosiect", + }, + }, + { + "section_name": {"en": "8. Check declarations", "cy": "8. Gwirio datganiadau"}, + "tree_path": f"{APPLICATION_BASE_PATH}.8", + }, + { + "section_name": {"en": "Declarations", "cy": "Datganiadau"}, + "tree_path": f"{APPLICATION_BASE_PATH}.8.1", + "form_name_json": {"en": "declarations", "cy": "datganiadau"}, + }, +] diff --git a/fund_store/config/fund_loader_config/cof/cof_r3.py b/fund_store/config/fund_loader_config/cof/cof_r3.py new file mode 100644 index 000000000..3bd3e214c --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/cof_r3.py @@ -0,0 +1,1022 @@ +# flake8: noqa +import textwrap +from datetime import datetime +from datetime import timezone + +from config.fund_loader_config.cof.shared import COF_APPLICATION_GUIDANCE +from config.fund_loader_config.cof.shared import fund_config +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + COF_R3_W1_BASE_PATH, +) +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + COF_R3_W2_BASE_PATH, +) +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + COF_R3_W3_BASE_PATH, +) + +COF_FUND_ID = fund_config["id"] +COF_ROUND_3_WINDOW_1_ID = "e85ad42f-73f5-4e1b-a1eb-6bc5d7f3d762" +COF_ROUND_3_WINDOW_2_ID = "6af19a5e-9cae-4f00-9194-cf10d2d7c8a7" +COF_ROUND_3_WINDOW_3_ID = "4efc3263-aefe-4071-b5f4-0910abec12d2" + +APPLICATION_BASE_PATH_COF_R3_W1 = ".".join([str(COF_R3_W1_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH_COF_R3_W1 = ".".join([str(COF_R3_W1_BASE_PATH), str(2)]) + +APPLICATION_BASE_PATH_COF_R3_W2 = ".".join([str(COF_R3_W2_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH_COF_R3_W2 = ".".join([str(COF_R3_W2_BASE_PATH), str(2)]) + +APPLICATION_BASE_PATH_COF_R3_W3 = ".".join([str(COF_R3_W3_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH_COF_R3_W3 = ".".join([str(COF_R3_W3_BASE_PATH), str(2)]) + +COF_R3W1_OPENS_DATE = datetime(2023, 5, 31, 11, 0, 0, tzinfo=timezone.utc) # 2023-05-31 11:00:00 +COF_R3W1_DEADLINE_DATE = datetime(2023, 7, 12, 11, 59, 0, tzinfo=timezone.utc) # 2023-07-12 11:59:00 +COF_R3W1_ASSESSMENT_DEADLINE_DATE = datetime(2023, 8, 9, 12, 0, 0, tzinfo=timezone.utc) # 2023-08-09 12:00:00 +COF_R3W2_OPENS_DATE = datetime(2023, 8, 30, 11, 0, 0, tzinfo=timezone.utc) # 2023-08-30 11:00:00 +COF_R3W2_DEADLINE_DATE = datetime(2023, 10, 11, 11, 59, 0, tzinfo=timezone.utc) # 2023-10-11 11:59:00 +COF_R3W2_ASSESSMENT_DEADLINE_DATE = datetime(2023, 11, 20, 12, 0, 0, tzinfo=timezone.utc) # 2023-11-20 12:00:00 +COF_R3W3_OPENS_DATE = datetime(2023, 12, 6, 11, 00, 0, tzinfo=timezone.utc) # 2023-12-06 11:00:00 +COF_R3W3_SEND_REMINDER_DATE = datetime(2024, 1, 29, 11, 59, 0, tzinfo=timezone.utc) # 2024-01-29 11:59:00 +COF_R3W3_DEADLINE_DATE = datetime(2024, 1, 31, 11, 59, 0, tzinfo=timezone.utc) # 2024-01-31 11:59:00 +COF_R3W3_ASSESSMENT_DEADLINE_DATE = datetime(2024, 2, 23, 12, 0, 0, tzinfo=timezone.utc) # 2024-02-23 12:00:00 + +COF_EOI_OPENS_DATE = datetime(2024, 3, 6, 11, 00, 0, tzinfo=timezone.utc) # 2023-12-06 11:00:00 +COF_EOI_DEADLINE_DATE = datetime(2124, 3, 6, 11, 59, 0, tzinfo=timezone.utc) # 2124-03-06 11:59:00 +COF_EOI_ASSESSMENT_DEADLINE_DATE = datetime(2124, 3, 6, 12, 0, 0, tzinfo=timezone.utc) # 2124-03-06 12:00:00 +COF_EOI_SEND_REMINDER_DATE = datetime(2024, 3, 1, 11, 59, 0, tzinfo=timezone.utc) # 2024-03-1 11:59:00 + +cof_r3_sections = [ + { + "section_name": { + "en": "1. About your organisation", + "cy": "1. Ynglŷn â'ch sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.1", + }, + { + "section_name": { + "en": "1.1 Organisation information", + "cy": "1.1 Gwybodaeth am y sefydliad", + }, + "form_name_json": { + "en": "organisation-information-cof-r3-w1", + "cy": "gwybodaeth-am-y-sefydliad-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.1.1", + }, + { + "section_name": { + "en": "1.2 Applicant information", + "cy": "1.2 Gwybodaeth am yr ymgeisydd", + }, + "form_name_json": { + "en": "applicant-information-cof-r3-w1", + "cy": "gwybodaeth-am-yr-ymgeisydd-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.1.2", + }, + { + "section_name": { + "en": "2. About your project", + "cy": "2. Ynglŷn â'ch prosiect", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.2", + }, + { + "section_name": { + "en": "2.1 Project information", + "cy": "2.1 Gwybodaeth am y prosiect", + }, + "form_name_json": { + "en": "project-information-cof-r3-w1", + "cy": "gwybodaeth-am-y-prosiect-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.2.1", + }, + { + "section_name": { + "en": "2.2 Asset information", + "cy": "2.2 Gwybodaeth am yr ased", + }, + "form_name_json": { + "en": "asset-information-cof-r3-w1", + "cy": "gwybodaeth-am-yr-ased-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.2.2", + }, + { + "section_name": {"en": "3. Strategic case", "cy": "3. Achos strategol"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.3", + "weighting": 53, + }, + { + "section_name": {"en": "3.1 Community use", "cy": "3.1 Defnydd cymunedol"}, + "form_name_json": { + "en": "community-use-cof-r3-w1", + "cy": "defnydd-cymunedol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.3.1", + }, + { + "section_name": { + "en": "3.2 Community engagement", + "cy": "3.2 Ymgysylltu â'r gymuned", + }, + "form_name_json": { + "en": "community-engagement-cof-r3-w1", + "cy": "ymgysylltiad-cymunedol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.3.2", + }, + { + "section_name": {"en": "3.3 Local support", "cy": "3.3 Cefnogaeth leol"}, + "form_name_json": { + "en": "local-support-cof-r3-w1", + "cy": "cefnogaeth-leol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.3.3", + }, + { + "section_name": { + "en": "3.4 Community benefits", + "cy": "3.4 Buddion i'r gymuned", + }, + "form_name_json": { + "en": "community-benefits-cof-r3-w1", + "cy": "buddion-cymunedol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.3.4", + }, + { + "section_name": { + "en": "3.5 Environmental sustainability", + "cy": "3.5 Cynaliadwyedd amgylcheddol", + }, + "form_name_json": { + "en": "environmental-sustainability-cof-r3-w1", + "cy": "cynaliadwyedd-amgylcheddol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.3.5", + }, + { + "section_name": {"en": "4. Management case", "cy": "4. Achos rheoli"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4", + "weighting": 47, + }, + { + "section_name": { + "en": "4.1 Funding required", + "cy": "4.1 Cyllid sydd ei angen", + }, + "form_name_json": { + "en": "funding-required-cof-r3-w1", + "cy": "cyllid-sydd-ei-angen-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.1", + }, + { + "section_name": {"en": "4.2 Feasibility", "cy": "4.2 Dichonoldeb"}, + "form_name_json": { + "en": "feasibility-cof-r3-w1", + "cy": "dichonoldeb-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.2", + }, + { + "section_name": {"en": "4.3 Risk", "cy": "4.3 Risg"}, + "form_name_json": {"en": "risk-cof-r3-w1", "cy": "risg-cof-r3-w1"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.3", + }, + { + "section_name": {"en": "4.4 Operational costs", "cy": "4.4 Costau gweithredol"}, + "form_name_json": { + "en": "operational-costs-cof-r3-w1", + "cy": "costau-gweithredol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.4", + }, + { + "section_name": { + "en": "4.5 Skills and resources", + "cy": "4.5 Sgiliau ac Adnoddau", + }, + "form_name_json": { + "en": "skills-and-resources-cof-r3-w1", + "cy": "sgiliau-ac-adnoddau-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.5", + }, + { + "section_name": { + "en": "4.6 Community representation", + "cy": "4.6 Cynrychiolaeth gymunedol", + }, + "form_name_json": { + "en": "community-representation-cof-r3-w1", + "cy": "cynrychiolaeth-gymunedol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.6", + }, + { + "section_name": { + "en": "4.7 Inclusiveness and integration", + "cy": "4.7 Cynhwysiant ac Integreiddio", + }, + "form_name_json": { + "en": "inclusiveness-and-integration-cof-r3-w1", + "cy": "cynhwysiant-ac-integreiddio-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.7", + }, + { + "section_name": { + "en": "4.8 Upload business plan", + "cy": "4.8 Lanlwythwch y cynllun busnes", + }, + "form_name_json": { + "en": "upload-business-plan-cof-r3-w1", + "cy": "lanlwythwch-y-cynllun-busnes-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.4.8", + }, + { + "section_name": { + "en": "5. Subsidy control and state aid", + "cy": "5. Rheoli cymorthdaliadau a chymorth gwladwriaethol", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.5", + }, + { + "section_name": { + "en": "5.1 Project qualification", + "cy": "5.1 Cymhwystra'r prosiect", + }, + "form_name_json": { + "en": "project-qualifications-cof-r3-w1", + "cy": "cymhwysedd-y-prosiect-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.5.1", + }, + { + "section_name": {"en": "6. Check declarations", "cy": "6. Gwirio datganiadau"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.6", + }, + { + "section_name": {"en": "6.1 Declarations", "cy": "6.1 Datganiadau"}, + "form_name_json": { + "en": "declarations-cof-r3-w1", + "cy": "cadarnhadau-terfynol-cof-r3-w1", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W1}.6.1", + }, +] + +cof_r3w2_sections = [ + { + "section_name": { + "en": "1. About your organisation", + "cy": "1. Ynglŷn â'ch sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.1", + "requires_feedback": True, + }, + { + "section_name": { + "en": "1.1 Organisation information", + "cy": "1.1 Gwybodaeth am y sefydliad", + }, + "form_name_json": { + "en": "organisation-information-cof-r3-w2", + "cy": "gwybodaeth-am-y-sefydliad-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.1.1", + }, + { + "section_name": { + "en": "1.2 Applicant information", + "cy": "1.2 Gwybodaeth am yr ymgeisydd", + }, + "form_name_json": { + "en": "applicant-information-cof-r3-w2", + "cy": "gwybodaeth-am-yr-ymgeisydd-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.1.2", + }, + { + "section_name": { + "en": "2. About your project", + "cy": "2. Ynglŷn â'ch prosiect", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.2", + "requires_feedback": True, + }, + { + "section_name": { + "en": "2.1 Project information", + "cy": "2.1 Gwybodaeth am y prosiect", + }, + "form_name_json": { + "en": "project-information-cof-r3-w2", + "cy": "gwybodaeth-am-y-prosiect-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.2.1", + }, + { + "section_name": { + "en": "2.2 Asset information", + "cy": "2.2 Gwybodaeth am yr ased", + }, + "form_name_json": { + "en": "asset-information-cof-r3-w2", + "cy": "gwybodaeth-am-yr-ased-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.2.2", + }, + { + "section_name": {"en": "3. Strategic case", "cy": "3. Achos strategol"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.3", + "requires_feedback": True, + "weighting": 53, + }, + { + "section_name": {"en": "3.1 Community use", "cy": "3.1 Defnydd cymunedol"}, + "form_name_json": { + "en": "community-use-cof-r3-w2", + "cy": "defnydd-cymunedol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.3.1", + }, + { + "section_name": { + "en": "3.2 Community engagement", + "cy": "3.2 Ymgysylltu â'r gymuned", + }, + "form_name_json": { + "en": "community-engagement-cof-r3-w2", + "cy": "ymgysylltiad-cymunedol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.3.2", + }, + { + "section_name": {"en": "3.3 Local support", "cy": "3.3 Cefnogaeth leol"}, + "form_name_json": { + "en": "local-support-cof-r3-w2", + "cy": "cefnogaeth-leol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.3.3", + }, + { + "section_name": { + "en": "3.4 Community benefits", + "cy": "3.4 Buddion i'r gymuned", + }, + "form_name_json": { + "en": "community-benefits-cof-r3-w2", + "cy": "buddion-cymunedol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.3.4", + }, + { + "section_name": { + "en": "3.5 Environmental sustainability", + "cy": "3.5 Cynaliadwyedd amgylcheddol", + }, + "form_name_json": { + "en": "environmental-sustainability-cof-r3-w2", + "cy": "cynaliadwyedd-amgylcheddol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.3.5", + }, + { + "section_name": {"en": "4. Management case", "cy": "4. Achos rheoli"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4", + "weighting": 47, + "requires_feedback": True, + }, + { + "section_name": { + "en": "4.1 Funding required", + "cy": "4.1 Cyllid sydd ei angen", + }, + "form_name_json": { + "en": "funding-required-cof-r3-w2", + "cy": "cyllid-sydd-ei-angen-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.1", + }, + { + "section_name": {"en": "4.2 Feasibility", "cy": "4.2 Dichonoldeb"}, + "form_name_json": { + "en": "feasibility-cof-r3-w2", + "cy": "dichonoldeb-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.2", + }, + { + "section_name": {"en": "4.3 Risk", "cy": "4.3 Risg"}, + "form_name_json": {"en": "risk-cof-r3-w2", "cy": "risg-cof-r3-w2"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.3", + }, + { + "section_name": {"en": "4.4 Operational costs", "cy": "4.4 Costau gweithredol"}, + "form_name_json": { + "en": "operational-costs-cof-r3-w2", + "cy": "costau-gweithredol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.4", + }, + { + "section_name": { + "en": "4.5 Skills and resources", + "cy": "4.5 Sgiliau ac Adnoddau", + }, + "form_name_json": { + "en": "skills-and-resources-cof-r3-w2", + "cy": "sgiliau-ac-adnoddau-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.5", + }, + { + "section_name": { + "en": "4.6 Community representation", + "cy": "4.6 Cynrychiolaeth gymunedol", + }, + "form_name_json": { + "en": "community-representation-cof-r3-w2", + "cy": "cynrychiolaeth-gymunedol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.6", + }, + { + "section_name": { + "en": "4.7 Inclusiveness and integration", + "cy": "4.7 Cynhwysiant ac Integreiddio", + }, + "form_name_json": { + "en": "inclusiveness-and-integration-cof-r3-w2", + "cy": "cynhwysiant-ac-integreiddio-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.7", + }, + { + "section_name": { + "en": "4.8 Upload business plan", + "cy": "4.8 Lanlwythwch y cynllun busnes", + }, + "form_name_json": { + "en": "upload-business-plan-cof-r3-w2", + "cy": "lanlwythwch-y-cynllun-busnes-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.4.8", + }, + { + "section_name": { + "en": "5. Subsidy control and state aid", + "cy": "5. Rheoli cymorthdaliadau a chymorth gwladwriaethol", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.5", + }, + { + "section_name": { + "en": "5.1 Project qualification", + "cy": "5.1 Cymhwystra'r prosiect", + }, + "form_name_json": { + "en": "project-qualifications-cof-r3-w2", + "cy": "cymhwysedd-y-prosiect-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.5.1", + }, + { + "section_name": {"en": "6. Check declarations", "cy": "6. Gwirio datganiadau"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.6", + }, + { + "section_name": {"en": "6.1 Declarations", "cy": "6.1 Datganiadau"}, + "form_name_json": { + "en": "declarations-cof-r3-w2", + "cy": "cadarnhadau-terfynol-cof-r3-w2", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W2}.6.1", + }, +] + +cof_r3w3_sections = [ + { + "section_name": { + "en": "1. About your organisation", + "cy": "1. Ynglŷn â'ch sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.1", + "requires_feedback": True, + }, + { + "section_name": { + "en": "1.1 Organisation information", + "cy": "1.1 Gwybodaeth am y sefydliad", + }, + "form_name_json": { + "en": "organisation-information-cof", + "cy": "gwybodaeth-am-y-sefydliad-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.1.1", + }, + { + "section_name": { + "en": "1.2 Applicant information", + "cy": "1.2 Gwybodaeth am yr ymgeisydd", + }, + "form_name_json": { + "en": "applicant-information-cof", + "cy": "gwybodaeth-am-yr-ymgeisydd-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.1.2", + }, + { + "section_name": { + "en": "2. About your project", + "cy": "2. Ynglŷn â'ch prosiect", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.2", + "requires_feedback": True, + }, + { + "section_name": { + "en": "2.1 Project information", + "cy": "2.1 Gwybodaeth am y prosiect", + }, + "form_name_json": { + "en": "project-information-cof", + "cy": "gwybodaeth-am-y-prosiect-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.2.1", + }, + { + "section_name": { + "en": "2.2 Asset information", + "cy": "2.2 Gwybodaeth am yr ased", + }, + "form_name_json": { + "en": "asset-information-cof", + "cy": "gwybodaeth-am-yr-ased-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.2.2", + }, + { + "section_name": {"en": "3. Strategic case", "cy": "3. Achos strategol"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.3", + "requires_feedback": True, + "weighting": 53, + }, + { + "section_name": {"en": "3.1 Community use", "cy": "3.1 Defnydd cymunedol"}, + "form_name_json": { + "en": "community-use-cof", + "cy": "defnydd-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.3.1", + }, + { + "section_name": { + "en": "3.2 Community engagement", + "cy": "3.2 Ymgysylltu â'r gymuned", + }, + "form_name_json": { + "en": "community-engagement-cof", + "cy": "ymgysylltiad-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.3.2", + }, + { + "section_name": {"en": "3.3 Local support", "cy": "3.3 Cefnogaeth leol"}, + "form_name_json": { + "en": "local-support-cof", + "cy": "cefnogaeth-leol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.3.3", + }, + { + "section_name": { + "en": "3.4 Community benefits", + "cy": "3.4 Buddion cymunedol", + }, + "form_name_json": { + "en": "community-benefits-cof", + "cy": "buddion-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.3.4", + }, + { + "section_name": { + "en": "3.5 Environmental sustainability", + "cy": "3.5 Cynaliadwyedd amgylcheddol", + }, + "form_name_json": { + "en": "environmental-sustainability-cof", + "cy": "cynaliadwyedd-amgylcheddol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.3.5", + }, + { + "section_name": {"en": "4. Management case", "cy": "4. Achos rheoli"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4", + "weighting": 47, + "requires_feedback": True, + }, + { + "section_name": { + "en": "4.1 Funding required", + "cy": "4.1 Cyllid sydd ei angen", + }, + "form_name_json": { + "en": "funding-required-cof", + "cy": "cyllid-sydd-ei-angen-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.1", + }, + { + "section_name": {"en": "4.2 Feasibility", "cy": "4.2 Dichonoldeb"}, + "form_name_json": { + "en": "feasibility-cof", + "cy": "dichonoldeb-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.2", + }, + { + "section_name": {"en": "4.3 Risk", "cy": "4.3 Risg"}, + "form_name_json": {"en": "risk-cof", "cy": "risg-cof"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.3", + }, + { + "section_name": {"en": "4.4 Operational costs", "cy": "4.4 Costau gweithredol"}, + "form_name_json": { + "en": "operational-costs-cof", + "cy": "costau-gweithredol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.4", + }, + { + "section_name": { + "en": "4.5 Skills and resources", + "cy": "4.5 Sgiliau ac adnoddau", + }, + "form_name_json": { + "en": "skills-and-resources-cof", + "cy": "sgiliau-ac-adnoddau-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.5", + }, + { + "section_name": { + "en": "4.6 Community representation", + "cy": "4.6 Cynrychiolaeth gymunedol", + }, + "form_name_json": { + "en": "community-representation-cof", + "cy": "cynrychiolaeth-gymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.6", + }, + { + "section_name": { + "en": "4.7 Inclusiveness and integration", + "cy": "4.7 Cynhwysiant ac integreiddio", + }, + "form_name_json": { + "en": "inclusiveness-and-integration-cof", + "cy": "cynhwysiant-ac-integreiddio-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.7", + }, + { + "section_name": { + "en": "4.8 Upload business plan", + "cy": "4.8 Lanlwythwch y cynllun busnes", + }, + "form_name_json": { + "en": "upload-business-plan-cof", + "cy": "lanlwythwch-y-cynllun-busnes-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.4.8", + }, + { + "section_name": {"en": "5. Check declarations", "cy": "5. Gwirio datganiadau"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.5", + }, + { + "section_name": {"en": "5.1 Declarations", "cy": "5.1 Datganiadau"}, + "form_name_json": { + "en": "declarations-cof", + "cy": "cadarnhadau-terfynol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R3_W3}.5.1", + }, +] + +round_config = [ + { + "id": COF_ROUND_3_WINDOW_1_ID, + "fund_id": COF_FUND_ID, + "title_json": {"en": "Round 3 Window 1", "cy": "Rownd 3 Cyfnod Ymgeisio 1"}, + "short_name": "R3W1", + "opens": COF_R3W1_OPENS_DATE, + "assessment_start": None, + "deadline": COF_R3W1_DEADLINE_DATE, + "application_reminder_sent": True, + "reminder_date": None, + "assessment_deadline": COF_R3W1_ASSESSMENT_DEADLINE_DATE, + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + "https://www.gov.uk/government/publications/community-ownership-fund-" + "privacy-notice/community-ownership-fund-privacy-notice" + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + "cy": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElURUY1WkhaS0NFMlZVQUhYQ1NaN0E4RjlQMC4u" + ), + "project_name_field_id": "apGjFS", + "application_guidance_json": COF_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://www.gov.uk/government/publications/community-ownership-fund-round-3-application-form" + "-assessment-criteria-guidance" + ), + "all_uploaded_documents_section_available": True, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": False, + "has_section_feedback": False, + "is_feedback_survey_optional": True, + "is_section_feedback_optional": True, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + } +] + +round_config_w2 = [ + { + "id": COF_ROUND_3_WINDOW_2_ID, + "fund_id": COF_FUND_ID, + "title_json": {"en": "Round 3 Window 2", "cy": "Rownd 3 Cyfnod Ymgeisio 2"}, + "short_name": "R3W2", + "opens": COF_R3W2_OPENS_DATE, + "assessment_start": None, + "deadline": COF_R3W2_DEADLINE_DATE, + "application_reminder_sent": True, + "reminder_date": None, + "assessment_deadline": COF_R3W2_ASSESSMENT_DEADLINE_DATE, + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + "https://www.gov.uk/government/publications/community-ownership-fund-" + "privacy-notice/community-ownership-fund-privacy-notice" + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + "cy": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElURUY1WkhaS0NFMlZVQUhYQ1NaN0E4RjlQMC4u" + ), + "project_name_field_id": "apGjFS", + "application_guidance_json": COF_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://www.gov.uk/government/publications/community-ownership-fund-round-3-application-form" + "-assessment-criteria-guidance" + ), + "all_uploaded_documents_section_available": True, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": True, + "has_section_feedback": True, + "is_feedback_survey_optional": False, + "is_section_feedback_optional": False, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + }, +] + +round_config_w3 = [ + { + "id": COF_ROUND_3_WINDOW_3_ID, + "fund_id": COF_FUND_ID, + "title_json": {"en": "Round 3 Window 3", "cy": "Rownd 3 Cyfnod Ymgeisio 3"}, + "short_name": "R3W3", + "opens": COF_R3W3_OPENS_DATE, + "assessment_start": None, + "deadline": COF_R3W3_DEADLINE_DATE, + "application_reminder_sent": False, + "reminder_date": COF_R3W3_SEND_REMINDER_DATE, + "assessment_deadline": COF_R3W3_ASSESSMENT_DEADLINE_DATE, + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + "https://www.gov.uk/government/publications/community-ownership-fund-" + "privacy-notice/community-ownership-fund-privacy-notice" + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + "cy": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElURUY1WkhaS0NFMlZVQUhYQ1NaN0E4RjlQMC4u" + ), + "project_name_field_id": "apGjFS", + "application_guidance_json": COF_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://www.gov.uk/government/publications/community-ownership-fund-round-3-application-form" + "-assessment-criteria-guidance" + ), + "all_uploaded_documents_section_available": True, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": True, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": True, + "has_section_feedback": True, + "is_feedback_survey_optional": False, + "is_section_feedback_optional": False, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + } +] diff --git a/fund_store/config/fund_loader_config/cof/cof_r4.py b/fund_store/config/fund_loader_config/cof/cof_r4.py new file mode 100644 index 000000000..a9ae685cc --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/cof_r4.py @@ -0,0 +1,670 @@ +# flake8: noqa +import textwrap +from datetime import datetime +from datetime import timezone + +from config.fund_loader_config.cof.shared import COF_APPLICATION_GUIDANCE +from config.fund_loader_config.cof.shared import fund_config +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + COF_R4_W1_BASE_PATH, +) +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + COF_R4_W2_BASE_PATH, +) + +COF_FUND_ID = fund_config["id"] +COF_ROUND_4_WINDOW_1_ID = "33726b63-efce-4749-b149-20351346c76e" +COF_ROUND_4_WINDOW_2_ID = "27ab26c2-e58e-4bfe-917d-64be10d16496" + +APPLICATION_BASE_PATH_COF_R4_W1 = ".".join([str(COF_R4_W1_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH_COF_R4_W1 = ".".join([str(COF_R4_W1_BASE_PATH), str(2)]) + +APPLICATION_BASE_PATH_COF_R4_W2 = ".".join([str(COF_R4_W2_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH_COF_R4_W2 = ".".join([str(COF_R4_W2_BASE_PATH), str(2)]) + + +COF_R4W1_OPENS_DATE = datetime(2024, 3, 25, 14, 00, 0, tzinfo=timezone.utc) # 2024-03-25 14:00:00 +COF_R4W1_SEND_REMINDER_DATE = datetime(2024, 4, 8, 11, 59, 0, tzinfo=timezone.utc) # 2024-04-08 11:59:00 +COF_R4W1_DEADLINE_DATE = datetime(2024, 4, 10, 14, 00, 0, tzinfo=timezone.utc) # 2024-04-10 14:00:00 +COF_R4W1_ASSESSMENT_DEADLINE_DATE = datetime(2024, 6, 23, 12, 0, 0, tzinfo=timezone.utc) # 2024-06-23 12:00:00 + + +COF_R4W2_OPENS_DATE = datetime(2024, 5, 22, 14, 00, 0, tzinfo=timezone.utc) # 2024-05-22 14:00:00 +COF_R4W2_SEND_REMINDER_DATE = datetime(2024, 5, 22, 11, 59, 0, tzinfo=timezone.utc) # 2024-05-22 11:59:00 +COF_R4W2_DEADLINE_DATE = datetime(2024, 6, 26, 14, 00, 0, tzinfo=timezone.utc) # 2024-06-26 14:00:00 +COF_R4W2_ASSESSMENT_START_DATE = datetime(2024, 5, 22, 14, 00, 0, tzinfo=timezone.utc) # 2024-05-22 14:00:00 +COF_R4W2_ASSESSMENT_DEADLINE_DATE = datetime(2024, 7, 31, 12, 0, 0, tzinfo=timezone.utc) # 2024-07-31 12:00:00 + +# Date far in the future as a temporary measure to stop this going live by accident +COF_R4W2_HOLD_DATE = datetime(2124, 1, 1, 11, 59, 0) # 2124-01-01 11:59:00 + + +cof_r4w1_sections = [ + { + "section_name": { + "en": "1. About your organisation", + "cy": "1. Ynglŷn â'ch sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.1", + "requires_feedback": True, + }, + { + "section_name": { + "en": "1.1 Organisation information", + "cy": "1.1 Gwybodaeth am y sefydliad", + }, + "form_name_json": { + "en": "organisation-information-cof", + "cy": "gwybodaeth-am-y-sefydliad-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.1.1", + }, + { + "section_name": { + "en": "1.2 Applicant information", + "cy": "1.2 Gwybodaeth am yr ymgeisydd", + }, + "form_name_json": { + "en": "applicant-information-cof", + "cy": "gwybodaeth-am-yr-ymgeisydd-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.1.2", + }, + { + "section_name": { + "en": "2. About your project", + "cy": "2. Ynglŷn â'ch prosiect", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.2", + "requires_feedback": True, + }, + { + "section_name": { + "en": "2.1 Project information", + "cy": "2.1 Gwybodaeth am y prosiect", + }, + "form_name_json": { + "en": "project-information-cof", + "cy": "gwybodaeth-am-y-prosiect-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.2.1", + }, + { + "section_name": { + "en": "2.2 Asset information", + "cy": "2.2 Gwybodaeth am yr ased", + }, + "form_name_json": { + "en": "asset-information-cof", + "cy": "gwybodaeth-am-yr-ased-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.2.2", + }, + { + "section_name": {"en": "3. Strategic case", "cy": "3. Achos strategol"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.3", + "requires_feedback": True, + "weighting": 53, + }, + { + "section_name": {"en": "3.1 Community use/significance", "cy": "3.1 Defnydd/arwyddocâd cymunedol"}, + "form_name_json": { + "en": "community-use-cof", + "cy": "defnydd-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.3.1", + }, + { + "section_name": { + "en": "3.2 Community engagement", + "cy": "3.2 Ymgysylltu â'r gymuned", + }, + "form_name_json": { + "en": "community-engagement-cof", + "cy": "ymgysylltiad-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.3.2", + }, + { + "section_name": {"en": "3.3 Local support", "cy": "3.3 Cefnogaeth leol"}, + "form_name_json": { + "en": "local-support-cof", + "cy": "cefnogaeth-leol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.3.3", + }, + { + "section_name": { + "en": "3.4 Community benefits", + "cy": "3.4 Buddion cymunedol", + }, + "form_name_json": { + "en": "community-benefits-cof", + "cy": "buddion-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.3.4", + }, + { + "section_name": { + "en": "3.5 Environmental sustainability", + "cy": "3.5 Cynaliadwyedd amgylcheddol", + }, + "form_name_json": { + "en": "environmental-sustainability-cof", + "cy": "cynaliadwyedd-amgylcheddol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.3.5", + }, + { + "section_name": {"en": "4. Management case", "cy": "4. Achos rheoli"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4", + "weighting": 47, + "requires_feedback": True, + }, + { + "section_name": { + "en": "4.1 Funding required", + "cy": "4.1 Cyllid sydd ei angen", + }, + "form_name_json": { + "en": "funding-required-cof", + "cy": "cyllid-sydd-ei-angen-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.1", + }, + { + "section_name": {"en": "4.2 Feasibility", "cy": "4.2 Dichonoldeb"}, + "form_name_json": { + "en": "feasibility-cof", + "cy": "dichonoldeb-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.2", + }, + { + "section_name": {"en": "4.3 Risk", "cy": "4.3 Risg"}, + "form_name_json": {"en": "risk-cof", "cy": "risg-cof"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.3", + }, + { + "section_name": {"en": "4.4 Operational costs", "cy": "4.4 Costau gweithredol"}, + "form_name_json": { + "en": "operational-costs-cof", + "cy": "costau-gweithredol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.4", + }, + { + "section_name": { + "en": "4.5 Skills and resources", + "cy": "4.5 Sgiliau ac adnoddau", + }, + "form_name_json": { + "en": "skills-and-resources-cof", + "cy": "sgiliau-ac-adnoddau-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.5", + }, + { + "section_name": { + "en": "4.6 Community representation", + "cy": "4.6 Cynrychiolaeth gymunedol", + }, + "form_name_json": { + "en": "community-representation-cof", + "cy": "cynrychiolaeth-gymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.6", + }, + { + "section_name": { + "en": "4.7 Inclusiveness and integration", + "cy": "4.7 Cynhwysiant ac integreiddio", + }, + "form_name_json": { + "en": "inclusiveness-and-integration-cof", + "cy": "cynhwysiant-ac-integreiddio-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.7", + }, + { + "section_name": { + "en": "4.8 Upload business plan", + "cy": "4.8 Lanlwythwch y cynllun busnes", + }, + "form_name_json": { + "en": "upload-business-plan-cof", + "cy": "lanlwythwch-y-cynllun-busnes-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.4.8", + }, + { + "section_name": {"en": "5. Check declarations", "cy": "5. Gwirio datganiadau"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.5", + }, + { + "section_name": {"en": "5.1 Declarations", "cy": "5.1 Datganiadau"}, + "form_name_json": { + "en": "declarations-cof", + "cy": "cadarnhadau-terfynol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W1}.5.1", + }, +] + +cof_r4w2_sections = [ + { + "section_name": { + "en": "1. About your organisation", + "cy": "1. Ynglŷn â'ch sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.1", + "requires_feedback": True, + }, + { + "section_name": { + "en": "1.1 Organisation information", + "cy": "1.1 Gwybodaeth am y sefydliad", + }, + "form_name_json": { + "en": "organisation-information-cof", + "cy": "gwybodaeth-am-y-sefydliad-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.1.1", + }, + { + "section_name": { + "en": "1.2 Applicant information", + "cy": "1.2 Gwybodaeth am yr ymgeisydd", + }, + "form_name_json": { + "en": "applicant-information-cof", + "cy": "gwybodaeth-am-yr-ymgeisydd-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.1.2", + }, + { + "section_name": { + "en": "2. About your project", + "cy": "2. Ynglŷn â'ch prosiect", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.2", + "requires_feedback": True, + }, + { + "section_name": { + "en": "2.1 Project information", + "cy": "2.1 Gwybodaeth am y prosiect", + }, + "form_name_json": { + "en": "project-information-cof", + "cy": "gwybodaeth-am-y-prosiect-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.2.1", + }, + { + "section_name": { + "en": "2.2 Asset information", + "cy": "2.2 Gwybodaeth am yr ased", + }, + "form_name_json": { + "en": "asset-information-cof", + "cy": "gwybodaeth-am-yr-ased-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.2.2", + }, + { + "section_name": {"en": "3. Strategic case", "cy": "3. Achos strategol"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.3", + "requires_feedback": True, + "weighting": 53, + }, + { + "section_name": {"en": "3.1 Community use/significance", "cy": "3.1 Defnydd/arwyddocâd cymunedol"}, + "form_name_json": { + "en": "community-use-cof", + "cy": "defnydd-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.3.1", + }, + { + "section_name": { + "en": "3.2 Community engagement", + "cy": "3.2 Ymgysylltu â'r gymuned", + }, + "form_name_json": { + "en": "community-engagement-cof", + "cy": "ymgysylltiad-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.3.2", + }, + { + "section_name": {"en": "3.3 Local support", "cy": "3.3 Cefnogaeth leol"}, + "form_name_json": { + "en": "local-support-cof", + "cy": "cefnogaeth-leol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.3.3", + }, + { + "section_name": { + "en": "3.4 Community benefits", + "cy": "3.4 Buddion cymunedol", + }, + "form_name_json": { + "en": "community-benefits-cof", + "cy": "buddion-cymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.3.4", + }, + { + "section_name": { + "en": "3.5 Environmental sustainability", + "cy": "3.5 Cynaliadwyedd amgylcheddol", + }, + "form_name_json": { + "en": "environmental-sustainability-cof", + "cy": "cynaliadwyedd-amgylcheddol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.3.5", + }, + { + "section_name": {"en": "4. Management case", "cy": "4. Achos rheoli"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4", + "weighting": 47, + "requires_feedback": True, + }, + { + "section_name": { + "en": "4.1 Funding required", + "cy": "4.1 Cyllid sydd ei angen", + }, + "form_name_json": { + "en": "funding-required-cof", + "cy": "cyllid-sydd-ei-angen-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.1", + }, + { + "section_name": {"en": "4.2 Feasibility", "cy": "4.2 Dichonoldeb"}, + "form_name_json": { + "en": "feasibility-cof", + "cy": "dichonoldeb-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.2", + }, + { + "section_name": {"en": "4.3 Risk", "cy": "4.3 Risg"}, + "form_name_json": {"en": "risk-cof", "cy": "risg-cof"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.3", + }, + { + "section_name": {"en": "4.4 Operational costs", "cy": "4.4 Costau gweithredol"}, + "form_name_json": { + "en": "operational-costs-cof", + "cy": "costau-gweithredol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.4", + }, + { + "section_name": { + "en": "4.5 Skills and resources", + "cy": "4.5 Sgiliau ac adnoddau", + }, + "form_name_json": { + "en": "skills-and-resources-cof", + "cy": "sgiliau-ac-adnoddau-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.5", + }, + { + "section_name": { + "en": "4.6 Community representation", + "cy": "4.6 Cynrychiolaeth gymunedol", + }, + "form_name_json": { + "en": "community-representation-cof", + "cy": "cynrychiolaeth-gymunedol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.6", + }, + { + "section_name": { + "en": "4.7 Inclusiveness and integration", + "cy": "4.7 Cynhwysiant ac integreiddio", + }, + "form_name_json": { + "en": "inclusiveness-and-integration-cof", + "cy": "cynhwysiant-ac-integreiddio-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.7", + }, + { + "section_name": { + "en": "4.8 Upload business plan", + "cy": "4.8 Lanlwythwch y cynllun busnes", + }, + "form_name_json": { + "en": "upload-business-plan-cof", + "cy": "lanlwythwch-y-cynllun-busnes-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.4.8", + }, + { + "section_name": {"en": "5. Check declarations", "cy": "5. Gwirio datganiadau"}, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.5", + }, + { + "section_name": {"en": "5.1 Declarations", "cy": "5.1 Datganiadau"}, + "form_name_json": { + "en": "declarations-cof", + "cy": "cadarnhadau-terfynol-cof", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_R4_W2}.5.1", + }, +] + +round_config_w1 = [ + { + "id": COF_ROUND_4_WINDOW_1_ID, + "fund_id": COF_FUND_ID, + "title_json": {"en": "Round 4 Window 1", "cy": "Rownd 4 Cyfnod Ymgeisio 1"}, + "short_name": "R4W1", + "opens": COF_R4W1_OPENS_DATE, + "assessment_start": None, + "deadline": COF_R4W1_DEADLINE_DATE, + "application_reminder_sent": False, + "reminder_date": COF_R4W1_SEND_REMINDER_DATE, + "assessment_deadline": COF_R4W1_ASSESSMENT_DEADLINE_DATE, + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + "https://www.gov.uk/government/publications/community-ownership-fund-" + "privacy-notice/community-ownership-fund-privacy-notice" + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + "cy": ( + "Mae'n rhaid i chi fod wedi derbyn gwahoddiad i ymgeisio. Os na wnaethom eich gwahodd, ' + " mynegwch eich diddordeb yn y gronfa yn gyntaf." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElURUY1WkhaS0NFMlZVQUhYQ1NaN0E4RjlQMC4u" + ), + "project_name_field_id": "apGjFS", + "application_guidance_json": COF_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://www.gov.uk/government/publications/community-ownership-fund-round-3-application-form" + "-assessment-criteria-guidance" + ), + "all_uploaded_documents_section_available": True, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": True, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": True, + "has_section_feedback": True, + "is_feedback_survey_optional": False, + "is_section_feedback_optional": False, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + } +] + +round_config_w2 = [ + { + "id": COF_ROUND_4_WINDOW_2_ID, + "fund_id": COF_FUND_ID, + "title_json": {"en": "Round 4 Window 2", "cy": "Rownd 4 Cyfnod Ymgeisio 2"}, + "short_name": "R4W2", + "opens": COF_R4W2_HOLD_DATE, + "assessment_start": COF_R4W2_HOLD_DATE, + "deadline": COF_R4W2_HOLD_DATE, + "application_reminder_sent": False, + "reminder_date": COF_R4W2_HOLD_DATE, + "assessment_deadline": COF_R4W2_HOLD_DATE, + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + "https://www.gov.uk/government/publications/community-ownership-fund-" + "privacy-notice/community-ownership-fund-privacy-notice" + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), + "cy": ( + "Mae'n rhaid i chi fod wedi derbyn gwahoddiad i ymgeisio. Os na wnaethom eich gwahodd, ' + " mynegwch eich diddordeb yn y gronfa yn gyntaf." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElURUY1WkhaS0NFMlZVQUhYQ1NaN0E4RjlQMC4u" + ), + "project_name_field_id": "apGjFS", + "application_guidance_json": COF_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://www.gov.uk/government/publications/community-ownership-fund-round-3-application-form" + "-assessment-criteria-guidance" + ), + "all_uploaded_documents_section_available": True, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": True, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": True, + "has_section_feedback": True, + "is_feedback_survey_optional": False, + "is_section_feedback_optional": False, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + } +] diff --git a/fund_store/config/fund_loader_config/cof/deprecated_fund_config/assessment_section_config.py b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/assessment_section_config.py new file mode 100644 index 000000000..eed933776 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/assessment_section_config.py @@ -0,0 +1,1305 @@ +# Extract sections at all levels as in application and alpha-numerically sort +# flake8: noqa +# Ignore line length + +scored_sections = [ + { + "id": "strategic_case", + "name": "Strategic case", + "weighting": 0.30, + "sub_criteria": [ + { + "id": "benefits", + "name": "Benefits", + "themes": [ + { + "id": "community_use", + "name": "Community use", + "answers": [ + { + "field_id": "kxgWTy", + "form_name": "community-use", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Who in the community uses the asset, or has used" + " it in the past, and who benefits from it?" + ), + }, + { + "field_id": "wudRxx", + "form_name": "project-information", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Tell us how the asset is currently being used, or" + " how it has been used before, and why it's" + " important to the community" + ), + }, + ], + }, + { + "id": "risk_loss_impact", + "name": "Risk and impact of loss", + "answers": [ + { + "field_id": "TlGjXb", + "form_name": "project-information", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Explain why the asset is at risk of being lost to" + " the community, or why it has already been lost" + ), + }, + { + "field_id": "UDTxqC", + "form_name": "asset-information", + "field_type": "checkboxesField", + "presentation_type": "list", + "question": "Why is the asset at risk of closure?", + }, + { + "field_id": "GNhrIs", + "form_name": "community-use", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Tell us how losing the asset would affect, or has" + " already affected, people in the community" + ), + }, + { + "field_id": "qsZLjZ", + "form_name": "community-use", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Why will the asset be lost without community intervention?", + }, + ], + }, + ], + }, + { + "id": "engagement", + "name": "Engagement", + "themes": [ + { + "id": "engaging-the-community", + "name": "Engaging the community", + "answers": [ + { + "field_id": "HJBgvw", + "form_name": "community-engagement", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Tell us how you have engaged with the community" + " about your intention to take ownership of the" + " asset, and explain how this has shaped your" + " project plans" + ), + }, + { + "field_id": "JCACTy", + "form_name": "community-engagement", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Have you done any fundraising in the community?", + # Yes-No determines dpLyQh + }, + { + "field_id": "dpLyQh", + "form_name": "community-engagement", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Describe your fundraising activities", + # Determined by Yes-No JCACTy + }, + ], + }, + { + "id": "local-support", + "name": "Local support", + "answers": [ + { + "field_id": "NZKHOp", + "form_name": "community-engagement", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Tell us how your project supports any wider local plans", + }, + { + "field_id": "KqoaJL", + "form_name": "local-support", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Are you confident there is local support for your project?", + # Yes-No determines KqoaJL + }, + { + "field_id": "BFbzux", + "form_name": "local-support", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Tell us more about the local support for your project", + # Determined by Yes-No KqoaJL + }, + { + "field_id": "EEBFao", + "form_name": "local-support", + "field_type": "fileUploadField", + "presentation_type": "file", + "question": "Upload supporting evidence (optional)", + }, + ], + }, + ], + }, + { + "id": "environmental_sustainability", + "name": "Environmental Sustainability", + "themes": [ + { + "id": "environmental-considerations", + "name": "Environmental considerations", + "answers": [ + { + "field_id": "CvVZJv", + "form_name": "environmental-sustainability", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Tell us how you have considered the environmental sustainability of your project" + ), + } + ], + } + ], + }, + ], + }, + { + "id": "management_case", + "name": "Management case", + "weighting": 0.30, + "sub_criteria": [ + { + "id": "funding_breakdown", + "name": "Funding breakdown", + "themes": [ + { + "id": "funding_requested", + "name": "Funding requested", + "answers": [ + { + # These 2 fields are capital and revenue funding respectively + "field_id": ["JzWvhj", "jLIgoi"], + "form_name": "", + "field_type": "numberField", + "presentation_type": "grouped_fields", + "question": [ + "Total funding request", + "Total funding request", + ], + }, + { + "field_id": "NdFwgy", + "form_name": "funding-required", + "field_type": "multiInputField", + "presentation_type": "heading", + "question": "Capital costs", + }, + { + "field_id": "NdFwgy", + "form_name": "funding-required", + "field_type": "textField", + "presentation_type": "description", + "question": "Describe the cost", + }, + { + "field_id": "NdFwgy", + "form_name": "funding-required", + "field_type": "numberField", + "presentation_type": "amount", + "question": "Amount", + }, + { + "field_id": "NWTKzQ", + "form_name": "funding-required", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Are you applying for revenue funding from the Community Ownership Fund?", + }, + { + "field_id": "NyudvF", + "form_name": "funding-required", + "field_type": "multiInputField", + "presentation_type": "heading", + "question": "Revenue costs", + }, + { + "field_id": "NyudvF", + "form_name": "funding-required", + "field_type": "textField", + "presentation_type": "description", + "question": "Describe the cost", + }, + { + "field_id": "NyudvF", + "form_name": "funding-required", + "field_type": "numberField", + "presentation_type": "amount", + "question": "Amount", + }, + { + "field_id": "DIZZOC", + "form_name": "funding-required", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Have you secured any match funding yet?", + }, + { + "field_id": "abkrwo", + "form_name": "funding-required", + "field_type": "multiInputField", + "presentation_type": "heading", + "question": "Secured match funding", + }, + { + "field_id": "abkrwo", + "form_name": "funding-required", + "field_type": "textField", + "presentation_type": "description", + "question": "Source of funding", + }, + { + "field_id": "abkrwo", + "form_name": "funding-required", + "field_type": "numberField", + "presentation_type": "amount", + "question": "Amount", + }, + { + "field_id": "RvbwSX", + "form_name": "funding-required", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Do you have any match funding identified but not yet secured?", + }, + { + "field_id": "AOLYnV", + "form_name": "funding-required", + "field_type": "multiInputField", + "presentation_type": "heading", + "question": "Unsecured match funding", + }, + { + "field_id": "AOLYnV", + "form_name": "funding-required", + "field_type": "textField", + "presentation_type": "description", + "question": "Source of funding", + }, + { + "field_id": "AOLYnV", + "form_name": "funding-required", + "field_type": "numberField", + "presentation_type": "amount", + "question": "Amount", + }, + { + "field_id": "fnIdkJ", + "form_name": "funding-required", + "field_type": "numberField", + "presentation_type": "text", + "question": "Asset value", + }, + { + "field_id": "oaIntA", + "form_name": "funding-required", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "If successful, will you use your funding in the next 12 months? (Y/N)", + }, + ], + } + ], + }, + { + "id": "financial_and_risk_forecasts", + "name": "Financial and risk forecasts", + "themes": [ + { + "id": "feasibility", + "name": "Feasibility", + "answers": [ + { + "field_id": "ieRCkI", + "form_name": "feasibility", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Tell us about the feasibility studies you have carried out for your project" + ), + }, + { + "field_id": "aAeszH", + "form_name": "feasibility", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Do you need to do any further feasibility work?", + }, + ], + }, + { + "id": "risk", + "name": "Risk", + "answers": [ + { + "field_id": "ozgwXq", + "form_name": "risk", + "field_type": "fileUploadField", + "presentation_type": "file", + "question": "Risks to your project (document upload)", + }, + ], + }, + { + "id": "income_and_running_costs", + "name": "Income and running costs", + "answers": [ + { + "field_id": "WDDkVB", + "form_name": "project-costs", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Summarise your cash flow for the running of the asset", + }, + { + "field_id": "TzOokX", + "form_name": "project-costs", + "field_type": "multiInputField", + "presentation_type": "heading", + "question": "Sources of income", + }, + { + "field_id": "TzOokX", + "form_name": "project-costs", + "field_type": "multiInputField", + "presentation_type": "description", + "question": "Income source", + }, + { + "field_id": "TzOokX", + "form_name": "project-costs", + "field_type": "multiInputField", + "presentation_type": "amount", + "question": "Amount", + }, + { + "field_id": "fbFeEx", + "form_name": "project-costs", + "field_type": "multiInputField", + "presentation_type": "heading", + "question": "Running costs", + }, + { + "field_id": "fbFeEx", + "form_name": "project-costs", + "field_type": "multiInputField", + "presentation_type": "description", + "question": "Running costs", + }, + { + "field_id": "fbFeEx", + "form_name": "project-costs", + "field_type": "multiInputField", + "presentation_type": "amount", + "question": "Amount", + }, + ], + }, + ], + }, + { + "id": "skills_and_resources", + "name": "Skills and resources", + "themes": [ + { + "id": "previous_experience", + "name": "Previous experience", + "answers": [ + { + "field_id": "BBlCko", + "form_name": "organisation-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Have you delivered projects like this before? (Y/N)", + }, + { + "field_id": ["wxCszQ", "QJFQgi", "DGNWqE"], + "form_name": "organisation-information", + "field_type": "multilineTextField", + "presentation_type": "grouped_fields", + "question": [ + "Describe your previous projects", + "Describe your previous projects", + ], + }, + { + "field_id": "CBIWnt", + "form_name": "skills-and-resources", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Do you have experience of managing a community asset?", + }, + { + "field_id": "QWveYc", + "form_name": "skills-and-resources", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Describe any experience you have with community assets similar to this", + }, + ], + }, + { + "id": "governance_and_structures", + "name": "Governance and structures", + "answers": [ + { + "field_id": "JnvsPq", + "form_name": "community-representation", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "List the members of your board", + }, + { + "field_id": "yMCivI", + "form_name": "community-representation", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Tell us about your governance and membership structures", + }, + ], + }, + { + "id": "recruitment", + "name": "Recruitment", + "answers": [ + { + "field_id": "vKnMPG", + "form_name": "skills-and-resources", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Do you have plans to recruit people to help you manage the asset?", + }, + { + "field_id": "VNjRgZ", + "form_name": "skills-and-resources", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Tells us about the roles you'll recruit", + }, + ], + }, + ], + }, + { + "id": "representation_inclusiveness_and_integration", + "name": "Representation, inclusiveness and integration", + "themes": [ + { + "id": "representing_community_views", + "name": "Representing community views", + "answers": [ + { + "field_id": "NUZOvS", + "form_name": "community-representation", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Explain how you'll consider the views of the community in the running of the asset" + ), + }, + ], + }, + { + "id": "accessibility_and_inclusivity", + "name": "Accessibility and inclusivity", + "answers": [ + { + "field_id": "YbfbSC", + "form_name": "inclusiveness-and-integration", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Describe anything that might prevent people from" + " using the asset or participating in its running" + ), + }, + { + "field_id": "KuhSWw", + "form_name": "inclusiveness-and-integration", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Tell us how you'll make your project accessible" + " and inclusive to everyone in the community" + ), + }, + { + "field_id": "bkJsiO", + "form_name": "inclusiveness-and-integration", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Describe how the project will bring people together from all over the community" + ), + }, + ], + }, + ], + }, + ], + }, + { + "id": "potential_to_deliver_community_benefit", + "name": "Potential to deliver community benefit", + "weighting": 0.30, + "sub_criteria": [ + { + "id": "community-benefits", + "name": "How the community benefits ", + "themes": [ + { + "id": "delivering_and_sustaining_benefits", + "name": "Delivering and sustaining benefits", + "answers": [ + { + "field_id": "SrtVAs", + "form_name": "inclusiveness_and_integration", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Describe the planned activities or services that will take place at the asset" + ), + }, + { + "field_id": "QjJtbs", + "form_name": "community_benefits", + "field_type": "checkboxesField", + "presentation_type": "list", + "question": "What community benefits do you expect to deliver with this project?", + }, + { + "field_id": "gDTsgG", + "form_name": "community_benefits", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Tell us about these benefits in detail, and" + " explain how you'll measure the benefits it'll" + " bring for the community" + ), + }, + { + "field_id": "kYjJFy", + "form_name": "community_benefits", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Explain how you plan to sustain, and potentially expand, these benefits over time" + ), + }, + ], + } + ], + }, + { + "id": "how-the-asset-will-be-inclusive", + "name": "How the asset will be inclusive", + "themes": [ + { + "id": "benefitting_the_whole_community", + "name": "Benefitting the whole community", + "answers": [ + { + "field_id": "UbjYqE", + "form_name": "community_benefits", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Tell us how you'll make sure the whole community benefits from the asset", + }, + ], + }, + ], + }, + ], + }, + { + "id": "added_value_of_the_community_asset", + "name": "Added value of the community asset", + "weighting": 0.10, + "sub_criteria": [ + { + "id": "value-to-the-community", + "name": "Value to the community", + "themes": [ + { + "id": "addressing_community_challenges", + "name": "Addressing community challenges", + "answers": [ + { + "field_id": "oOPUXI", + "form_name": "value-to-the-community", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": "Tell us about your local community as a whole", + }, + { + "field_id": "NKOmNL", + "form_name": "value-to-the-community", + "field_type": "multilineTextField", + "presentation_type": "text", + "question": ( + "Describe any specific challenges your community" + " faces, and how the asset will address these" + ), + }, + ], + } + ], + } + ], + }, +] + +unscored_sections = [ + { + "id": "unscored", + "name": "Unscored", + "sub_criteria": [ + { + "id": "org_info", + "name": "Organisation information", + "themes": [ + { + "id": "general_info", + "name": "General information", + "answers": [ + { + "field_id": "WWWWxy", + "form_name": "organisation-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Your unique tracker number", + }, + { + "field_id": "YdtlQZ", + "form_name": "organisation-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Organisation name", + }, + { + "field_id": "iBCGxY", + "form_name": "organisation-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Does your organisation use any other names", + }, + { + "field_id": ["PHFkCs", "QgNhXX", "XCcqae"], + "form_name": "organisation-information", + "field_type": "textField", + "presentation_type": "grouped_fields", + "question": [ + "Alternative names of your organisation", + "Alternative names of your organisation", + ], + }, + { + "field_id": ["lajFtB", "plmwJv"], + "form_name": "organisation-information", + "field_type": "radiosField", + "presentation_type": "grouped_fields", + "question": [ + "Type of organisation", + "Type of organisation", + ], + }, + { + "field_id": "GlPmCX", + "form_name": "organisation-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Company registration number", + }, + { + "field_id": ["GvPSna", "zsbmRx"], + "form_name": "organisation-information", + "field_type": "radiosField", + "presentation_type": "grouped_fields", + "question": [ + "Which regulatory body is your company registered with?", + "Which regulatory body is your company registered with?", + ], + }, + { + "field_id": "aHIGbK", + "form_name": "organisation-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Charity number", + }, + { + "field_id": "DwfHtk", + "form_name": "organisation-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Is your organisation a trading subsidiary of a parent company?", + }, + { + "field_id": "MPNlZx", + "form_name": "organisation-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Name of parent organisation", + }, + { + "field_id": "MyiYMw", + "form_name": "organisation-information", + "field_type": "datePartsField", + "presentation_type": "text", + "question": "Date parent organisation was established", + }, + { + "field_id": "ZQolYb", + "form_name": "organisation-information", + "field_type": "UkAddressField", + "presentation_type": "address", + "question": "Organisation address", + }, + { + "field_id": "zsoLdf", + "form_name": "organisation-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Is your correspondence address different to the organisation address?", + }, + { + "field_id": "VhkCbM", + "form_name": "organisation-information", + "field_type": "UkAddressField", + "presentation_type": "address", + "question": "Correspondence address", + }, + { + "field_id": ["FhbaEy", "FcdKlB", "BzxgDA"], + "form_name": "organisation-information", + "field_type": "websiteField", + "presentation_type": "grouped_fields", + "question": [ + "Website and social media", + "Website and social media", + ], + }, + ], + }, + { + "id": "activities", + "name": "Activities", + "answers": [ + { + "field_id": "emVGxS", + "form_name": "organisation-information", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": "What is your organisation's main purpose?", + }, + { + "field_id": ["btTtIb", "SkocDi", "CNeeiC"], + "form_name": "organisation-information", + "field_type": "multilineTextField", + "presentation_type": "grouped_fields", + "question": [ + "Tell us about your organisation's main activities", + "Tell us about your organisation's main activities", + ], + }, + ], + }, + { + "id": "partnerships", + "name": "Partnerships", + "answers": [ + { + "field_id": "hnLurH", + "form_name": "organisation-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Is your application a joint bid in partnership with other organisations?", + }, + { + "field_id": "APSjeB", + "form_name": "organisation-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Partner organisation name", + }, + { + "field_id": "biTJjF", + "form_name": "organisation-information", + "field_type": "UkAddressField", + "presentation_type": "address", + "question": "Partner organisation address", + }, + { + "field_id": "IkmvEt", + "form_name": "organisation-information", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": "Tell us about your partnership and how you plan to work together", + }, + ], + }, + ], + }, + { + "id": "applicant_info", + "name": "Applicant information", + "themes": [ + { + "id": "contact_information", + "name": "Contact information", + "answers": [ + { + "field_id": "ZBjDTn", + "form_name": "applicant-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Name of lead contact", + }, + { + "field_id": "LZBrEj", + "form_name": "applicant-information", + "field_type": "emailAddressField", + "presentation_type": "text", + "question": "Lead contact email address", + }, + { + "field_id": "lRfhGB", + "form_name": "applicant-information", + "field_type": "telephoneNumberField", + "presentation_type": "text", + "question": "Lead contact telephone number", + }, + ], + }, + ], + }, + { + "id": "project_info", + "name": "Project information", + "themes": [ + { + "id": "previous_funding", + "name": "Previous funding", + "answers": [ + { + "field_id": "gScdbf", + "form_name": "project-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Have you been given funding through the Community Ownership Fund before?", + }, + { + "field_id": "IrIYcA", + "form_name": "project-information", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": "Describe your previous project", + }, + { + "field_id": "TFdnGq", + "form_name": "project-information", + "field_type": "numberField", + "presentation_type": "text", + "question": "Amount of funding received", + }, + ], + }, + { + "id": "project_summary", + "name": "Project summary", + "answers": [ + { + "field_id": "KAgrBz", + "form_name": "project-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Project name", + }, + { + "field_id": "GCjCse", + "form_name": "project-information", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": "Give a brief summary of your project, including what you hope to achieve", + }, + ], + }, + ], + }, + { + "id": "asset_info", + "name": "Asset information", + "themes": [ + { + "id": "asset_ownership", + "name": "Asset ownership", + "answers": [ + { + "field_id": "VWkLlk", + "form_name": "asset-information", + "field_type": "radiosField", + "presentation_type": "text", + "question": "What do you intend to do with the asset?", + }, + { + "field_id": "IRfSZp", + "form_name": "asset-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Do you know who currently owns your asset?", + }, + { + "field_id": "ymlmrX", + "form_name": "asset-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Name of current asset owner", + }, + { + "field_id": "FtDJfK", + "form_name": "asset-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Describe the current ownership status", + }, + { + "field_id": "gkulUE", + "form_name": "asset-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Have you already completed the purchase or lease?", + }, + { + "field_id": "uBXptf", + "form_name": "asset-information", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": ( + "Describe the sale process, e.g. an auction, or the" + " terms of your lease if you have rented the asset" + ), + }, + { + "field_id": "nvMmGE", + "form_name": "asset-information", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": ( + "Describe the expected sale process, or the" + " proposed terms of your lease if you are renting" + " the asset" + ), + }, + { + "field_id": "ghzLRv", + "form_name": "asset-information", + "field_type": "datePartsField", + "presentation_type": "text", + "question": "Expected date of sale or lease", + }, + { + "field_id": "Wyesgy", + "form_name": "asset-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Is your asset currently publicly owned?", + }, + { + "field_id": "fHvilU", + "form_name": "asset-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Name of contact", + }, + { + "field_id": "scYeIU", + "form_name": "asset-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Job title of contact", + }, + { + "field_id": "ZHPwln", + "form_name": "asset-information", + "field_type": "textField", + "presentation_type": "text", + "question": "Organisation name", + }, + { + "field_id": "nkPfyn", + "form_name": "asset-information", + "field_type": "checkboxesField", + "presentation_type": "list", + "question": ( + "When you buy or lease a publicly owned asset, the" + " public authority cannot transfer statutory" + " services or duties to the community group" + ), + }, + { + "field_id": "PraPAq", + "form_name": "asset-information", + "field_type": "checkboxesField", + "presentation_type": "list", + "question": ( + "Grants from this fund cannot be used to buy the" + " freehold or premium on the lease of a publicly" + " owned asset. Money must only be used for" + " renovation and refurbishment costs" + ), + }, + ], + }, + { + "id": "asset_evidence", + "name": "Asset evidence", + "answers": [ + { + "field_id": "ArVrka", + "form_name": "asset-information", + "field_type": "fileUploadField", + "presentation_type": "file", + "question": "Supporting evidence", + } + ], + }, + { + "id": "asset_background", + "name": "Asset background", + "answers": [ + { + "field_id": ["yaQoxU", "GjzaqR"], + "form_name": "asset-information", + "field_type": "textField", + "presentation_type": "grouped_fields", + "question": ["Asset type", "Asset type"], + }, + { + "field_id": "hvzzWB", + "form_name": "asset-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Is this a registered Asset of Community Value?", + }, + { + "field_id": "MLwpjP", + "form_name": "asset-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Will you purchase the asset within the appropriate time frame?", + }, + { + "field_id": "VwxiGn", + "form_name": "asset-information", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Is the asset listed for disposal, or part of a Community Asset Transfer?", + }, + { + "field_id": "bkbGIE", + "form_name": "asset-information", + "field_type": "datePartsField", + "presentation_type": "text", + "question": "When was the asset listed?", + }, + { + "field_id": "kBCjwC", + "form_name": "asset-information", + "field_type": "websiteField", + "presentation_type": "text", + "question": "Provide a link to the listing", + }, + { + "field_id": "vKSMwi", + "form_name": "asset-information", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": "Describe the current status of the Community Asset Transfer", + }, + ], + }, + { + "id": "asset_location", + "name": "Asset location", + "answers": [ + { + "field_id": "yEmHpp", + "form_name": "project-information", + "field_type": "UkAddressField", + "presentation_type": "address", + "question": "Address of the community asset", + }, + { + "field_id": "iTeLGU", + "form_name": "project-information", + "field_type": "textField", + "presentation_type": "text", + "question": "In which constituency is your asset?", + }, + { + "field_id": "MGRlEi", + "form_name": "project-information", + "field_type": "textField", + "presentation_type": "text", + "question": "In which local council area is your asset?", + }, + ], + }, + ], + }, + { + "id": "business_plan", + "name": "Business plan", + "themes": [ + { + "id": "business_plan", + "name": "Business plan", + "answers": [ + { + "field_id": "rFXeZo", + "form_name": "upload-business-plan", + "field_type": "fileUploadField", + "presentation_type": "file", + "question": "Business plan (document upload)", + }, + ], + } + ], + }, + ], + }, + { + "id": "declarations", + "name": "Declarations", + "sub_criteria": [ + { + "id": "declarations", + "name": "Declarations", + "themes": [ + { + "id": "declarations", + "name": "Declarations", + "answers": [ + { + "field_id": "LlvhYl", + "form_name": "declarations", + "field_type": "yesNoField", + "presentation_type": "text", + "question": ( + "Confirm you have considered subsidy control and" + " state aid implications for your project, and the" + " information you have given us is correct" + ), + }, + { + "field_id": "wJrJWY", + "form_name": "declarations", + "field_type": "yesNoField", + "presentation_type": "text", + "question": ( + "Confirm you have considered people with protected" + " characteristics throughout the planning of your" + " project" + ), + }, + { + "field_id": "COiwQr", + "form_name": "declarations", + "field_type": "yesNoField", + "presentation_type": "text", + "question": ( + "Confirm you have considered sustainability and the" + " environment throughout the planning of your" + " project, including compliance with the" + " government's Net Zero ambitions" + ), + }, + { + "field_id": "bRPzWU", + "form_name": "declarations", + "field_type": "yesNoField", + "presentation_type": "text", + "question": ( + "Confirm you have a bank account set up and" + " associated with the organisation you are applying" + " on behalf of" + ), + }, + ], + } + ], + }, + { + "id": "subsidy_control_and_state_aid", + "name": "Subsidy control and state aid", + "themes": [ + { + "id": "project_qualification", + "name": "Project qualification", + "answers": [ + { + "field_id": "HvxXPI", + "form_name": "project-qualification", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Does your project meet the definition of a subsidy?", + }, + { + "field_id": "RmMKzM", + "form_name": "project-qualification", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": ( + "Explain how you think a grant from this fund can" + " be provided in compliance with the Subsidy" + " Control Act (2022)" + ), + }, + { + "field_id": "UPmQrD", + "form_name": "project-qualification", + "field_type": "yesNoField", + "presentation_type": "text", + "question": "Is your project based in Northern Ireland?", + }, + { + "field_id": "xPkdRX", + "form_name": "project-qualification", + "field_type": "multilineTextField", + "presentation_type": "list", + "question": "Explain how your project will comply with state aid rules", + }, + ], + } + ], + }, + ], + }, +] diff --git a/fund_store/config/fund_loader_config/cof/deprecated_fund_config/cof_form_config.py b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/cof_form_config.py new file mode 100644 index 000000000..04755ebd9 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/cof_form_config.py @@ -0,0 +1,117 @@ +COF_R2_ORDERED_FORMS_CONFIG = ( + { + "section_title": { + "en": "About your organisation", + "cy": "Ynglŷn â'ch sefydliad", + }, + "ordered_form_names_within_section": [ + { + "en": "organisation-information", + "cy": "gwybodaeth-am-y-sefydliad", + }, + { + "en": "applicant-information", + "cy": "gwybodaeth-am-yr-ymgeisydd", + }, + ], + "section_weighting": None, + }, + { + "section_title": { + "en": "About your project", + "cy": "Ynglŷn â'ch prosiect", + }, + "ordered_form_names_within_section": [ + { + "en": "project-information", + "cy": "gwybodaeth-am-y-prosiect", + }, + {"en": "asset-information", "cy": "gwybodaeth-am-yr-ased"}, + ], + "section_weighting": None, + }, + { + "section_title": {"en": "Strategic case", "cy": "Achos strategol"}, + "ordered_form_names_within_section": [ + {"en": "community-use", "cy": "defnydd-cymunedol"}, + {"en": "community-engagement", "cy": "ymgysylltu-a'r-gymuned"}, + {"en": "local-support", "cy": "cefnogaeth-leol"}, + { + "en": "environmental-sustainability", + "cy": "cynaliadwyedd-amgylcheddol", + }, + ], + "section_weighting": 30, + }, + { + "section_title": {"en": "Management case", "cy": "Achos rheoli"}, + "ordered_form_names_within_section": [ + {"en": "funding-required", "cy": "cyllid-sydd-ei-angen"}, + {"en": "feasibility", "cy": "dichonoldeb"}, + {"en": "risk", "cy": "risg"}, + {"en": "project-costs", "cy": "costau'r-prosiect"}, + {"en": "skills-and-resources", "cy": "sgiliau-ac-adnoddau"}, + { + "en": "community-representation", + "cy": "cynrychiolaeth-gymunedol", + }, + { + "en": "inclusiveness-and-integration", + "cy": "cynhwysiant-ac-integreiddio", + }, + { + "en": "upload-business-plan", + "cy": "lanlwythwch-y-cynllun-busnes", + }, + ], + "section_weighting": 30, + }, + { + "section_title": { + "en": "Potential to deliver community benefits", + "cy": "Potensial i gyflawni buddion cymunedol", + }, + "ordered_form_names_within_section": [ + {"en": "community-benefits", "cy": "buddion-cymunedol"}, + ], + "section_weighting": 30, + }, + { + "section_title": { + "en": "Added value to community", + "cy": "Gwerth ychwanegol i'r gymuned", + }, + "ordered_form_names_within_section": [ + {"en": "value-to-the-community", "cy": "gwerth-i'r-gymuned"}, + ], + "section_weighting": 10, + }, + { + "section_title": { + "en": "Subsidy control / state aid", + "cy": "Rheoli cymorthdaliadau a chymorth gwladwriaethol", + }, + "ordered_form_names_within_section": [ + {"en": "project-qualification", "cy": "cymhwystra'r-prosiect"}, + ], + "section_weighting": None, + }, + { + "section_title": { + "en": "Check declarations", + "cy": "Gwirio datganiadau", + }, + "ordered_form_names_within_section": [ + {"en": "declarations", "cy": "datganiadau"}, + ], + "section_weighting": None, + }, +) + +# --------------- +# Fund Config +# --------------- + +COF_FUND_ID = "47aef2f5-3fcb-4d45-acb5-f0152b5f03c4" +COF_ROUND_2_ID = "c603d114-5364-4474-a0c4-c41cbf4d3bbd" +COF_ROUND_2_W3_ID = "5cf439bf-ef6f-431e-92c5-a1d90a4dd32f" diff --git a/fund_store/config/fund_loader_config/cof/deprecated_fund_config/sort_application_sections.py b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/sort_application_sections.py new file mode 100755 index 000000000..293c9ff75 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/sort_application_sections.py @@ -0,0 +1,31 @@ +def alpha_numeric_sort_section(index, section_config, tree_base_path): + an_sorted_sections = [] + top_section_tree_level = f"{tree_base_path}.{index + 1}" + an_sorted_sections.append( + { + "section_name": str(section_config["section_title"]["en"]), + "tree_path": str(top_section_tree_level), + } + ) + + for index, form_section in enumerate(section_config["ordered_form_names_within_section"]): + current_form_section_tree_level = index + 1 + an_sorted_sections.append( + { + "section_name": str(form_section["en"]).replace("-", " ").title(), + "tree_path": str(f"{top_section_tree_level}.{current_form_section_tree_level}"), + "form_name": form_section["en"], + } + ) + return an_sorted_sections + + +def sort_sections_from_config(sections_config, tree_base_path): + all_an_sorted_sections = [] + for index, sc in enumerate(sections_config): + all_an_sorted_sections += alpha_numeric_sort_section(index, sc, tree_base_path) + return all_an_sorted_sections + + +def return_numerically_sorted_section_for_application(application_display_config, tree_base_path): + return {"sorted_sections": sort_sections_from_config(application_display_config, tree_base_path)} diff --git a/fund_store/config/fund_loader_config/cof/deprecated_fund_config/sort_assessment_sections.py b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/sort_assessment_sections.py new file mode 100644 index 000000000..76dcf7497 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/sort_assessment_sections.py @@ -0,0 +1,104 @@ +from config.fund_loader_config.cof.deprecated_fund_config.assessment_section_config import ( + scored_sections, + unscored_sections, +) + + +def map_fields(fields, all_fields): + ordered_fields = [] + for index, field in enumerate(fields): + # check to see if there are grouped fields + # they should be shown in a single assessment component - grouped + # therefore give them the same display order + if type(field["field_id"]) is list: + for index, grouped_field in enumerate(field["field_id"]): + ordered_fields.append({"form_json_id": grouped_field, "display_order": 10 * (index + 1)}) + all_fields.append( + { + "form_json_id": grouped_field, + "type": field["field_type"], + "presentation_type": field["presentation_type"], + "title": field["question"], + } + ) + else: + ordered_fields.append({"form_json_id": field["field_id"], "display_order": 10 * (index + 1)}) + all_fields.append( + { + "form_json_id": field["field_id"], + "type": field["field_type"], + "presentation_type": field["presentation_type"], + "title": field["question"], + } + ) + + return ordered_fields + + +def alpha_numeric_sort_section( + index, + section_config, + all_an_sorted_sections, + all_fields, + nested_keys_in_config, + parent_tree_path, + depth_count, +): + # resets when we drop down to the next tree level + current_tree_path = index + 1 + + # depth count tracks the level of recursive call + if depth_count < len(nested_keys_in_config): + next_level_key = nested_keys_in_config[depth_count] + depth_count += 1 + else: + next_level_key = None + + # If there is a parent tree path then this should prepend the current tree path + tree_path = f"{parent_tree_path}.{current_tree_path}" if parent_tree_path else current_tree_path + + # Add current section + new_section = { + "section_name": section_config["id"], + "tree_path": tree_path, + "weighting": section_config["weighting"] if "weighting" in section_config else None, + } + + # Check if this section has field_ids and then add section + if "answers" in section_config: + all_an_sorted_sections += [{**new_section, "fields": map_fields(section_config["answers"], all_fields)}] + else: + all_an_sorted_sections += [{**new_section}] + + # Before continuing to the next iteration at this level, check is there are any sub_levels to iterate through + if next_level_key: + for index, sub_section_config in enumerate(section_config[next_level_key]): + alpha_numeric_sort_section( + index, + sub_section_config, + all_an_sorted_sections, + all_fields, + nested_keys_in_config, + tree_path, + depth_count, + ) + + +def sort_sections_from_config(sections_config, sub_keys, all_fields, starting_path): + all_an_sorted_sections = [] + for index, sc in enumerate(sections_config): + alpha_numeric_sort_section(index, sc, all_an_sorted_sections, all_fields, sub_keys, starting_path, 0) + return all_an_sorted_sections + + +def return_numerically_sorted_section_for_assessment(scored_sections, unscored_sections): + sub_keys = ["sub_criteria", "themes"] + all_fields = [] + return { + "sorted_scored_sections": sort_sections_from_config(scored_sections, sub_keys, all_fields, 1), + "sorted_unscored_sections": sort_sections_from_config(unscored_sections, sub_keys, all_fields, 2), + "all_fields": all_fields, + } + + +sorted_sections_and_field_ids = return_numerically_sorted_section_for_assessment(scored_sections, unscored_sections) diff --git a/fund_store/config/fund_loader_config/cof/deprecated_fund_config/what_to_do_with_output.py b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/what_to_do_with_output.py new file mode 100644 index 000000000..4caa1fba3 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/deprecated_fund_config/what_to_do_with_output.py @@ -0,0 +1,148 @@ +# flake8: noqa +# The following lists are samples of the extracted data from the current section display config +# for both application and assessment +# Use the functions in the following files to get the data required for database input: +# 1. sort_application_sections.py +# 2. sort_assessment_sections.py +# ^ These functions are only necessary to transform existing config +# A sample data output of functions 1 and 2 is below +# It is envisaged that this data format below (and the steps outlined) will be the required input config for future rounds +### APPLICATION ### +# an_sorted_application_sections_SAMPLE = [ +# {'section_name': 'About your organisation', 'tree_path': '1'}, +# {'section_name': 'Organisation Information', 'tree_path': '1.1'}, +# {'section_name': 'Applicant Information', 'tree_path': '1.2'}, +# {'section_name': 'About your project', 'tree_path': '2'}, +# {'section_name': 'Project Information', 'tree_path': '2.1'}, +# {'section_name': 'Asset Information', 'tree_path': '2.2'}, +# {'section_name': 'Strategic case', 'tree_path': '3'}, +# {'section_name': 'Community Use', 'tree_path': '3.1'}, +# {'section_name': 'Community Engagement', 'tree_path': '3.2'}, +# {'section_name': 'Local Support', 'tree_path': '3.3'}, +# {'section_name': 'Environmental Sustainability', 'tree_path': '3.4'}, +# {'section_name': 'Management case', 'tree_path': '4'}, +# {'section_name': 'Funding Required', 'tree_path': '4.1'}, +# {'section_name': 'Feasibility', 'tree_path': '4.2'} +# # ...etc +# ] +# ### SQL +# # 3. Insert into SECTIONS_TABLE +# # for section in an_sorted_sections : +# # INSERT INTO SECTIONS_TABLE(path_value, section_names, service) +# # values (section["tree_path"], section["section_name"], assessmentORapplication); +# ### ASSESSMENT ### +# # The assessment script outputs three items for database insert (they are explained in the scripts further down this page) +# # { +# # "sorted_scored_sections": [...], +# # "sorted_unscored_sections": [...], +# # "all_fields": [...] +# # } +# all_an_sorted_sections_scored_SAMPLE = [ +# {'section_name': 'strategic_case', 'tree_path': '1.1', 'weighting': 0.3}, +# {'section_name': 'benefits', 'tree_path': '1.1.1', 'weighting': None}, +# {'section_name': 'community_use', 'tree_path': '1.1.1.1', 'weighting': None, 'fields': [ +# {'id': 'WWWWxy', 'display_order': 10}, +# {'id': 'YdtlQZ', 'display_order': 20}, +# {'id': 'iBCGxY', 'display_order': 30}, +# {'id': 'PHFkCs', 'display_order': 10}, +# {'id': 'QgNhXX', 'display_order': 20}, +# {'id': 'XCcqae', 'display_order': 30}, +# {'id': 'lajFtB', 'display_order': 10}, +# {'id': 'plmwJv', 'display_order': 20}, +# {'id': 'GlPmCX', 'display_order': 60}, +# {'id': 'GvPSna', 'display_order': 10}, +# {'id': 'zsbmRx', 'display_order': 20}, +# {'id': 'aHIGbK', 'display_order': 80}, +# {'id': 'DwfHtk', 'display_order': 90}, +# {'id': 'MPNlZx', 'display_order': 100}, +# ... +# ]}, +# {'section_name': 'risk_loss_impact', 'tree_path': '1.1.1.2', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'engagement', 'tree_path': '1.1.2', 'weighting': None}, +# {'section_name': 'engaging-the-community', +# 'tree_path': '1.1.2.1', 'weighting': None, 'fields': [...]}, +# {'section_name': 'local-support', 'tree_path': '1.1.2.2', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'environmental_sustainability', +# 'tree_path': '1.1.3', 'weighting': None}, +# {'section_name': 'environmental-considerations', +# 'tree_path': '1.1.3.1', 'weighting': None, 'fields': [...]}, +# {'section_name': 'management_case', 'tree_path': '1.2', 'weighting': 0.3}, +# {'section_name': 'funding_breakdown', 'tree_path': '1.2.1', 'weighting': None}, +# {'section_name': 'funding_requested', 'tree_path': '1.2.1.1', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'financial_and_risk_forecasts', +# 'tree_path': '1.2.2', 'weighting': None}, +# {'section_name': 'feasibility', 'tree_path': '1.2.2.1', +# 'weighting': None, 'fields': [...]}, +# ...] +# all_an_sorted_sections_unscored_SAMPLE = [ +# {'section_name': 'unscored', 'tree_path': '2.1', 'weighting': None}, +# {'section_name': 'org_info', 'tree_path': '2.1.1', 'weighting': None}, +# {'section_name': 'general_info', 'tree_path': '2.1.1.1', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'activities', 'tree_path': '2.1.1.2', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'partnerships', 'tree_path': '2.1.1.3', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'applicant_info', 'tree_path': '2.1.2', 'weighting': None}, +# {'section_name': 'contact_information', 'tree_path': '2.1.2.1', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'project_info', 'tree_path': '2.1.3', 'weighting': None}, +# {'section_name': 'previous_funding', 'tree_path': '2.1.3.1', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'project_summary', 'tree_path': '2.1.3.2', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'asset_info', 'tree_path': '2.1.4', 'weighting': None}, +# {'section_name': 'asset_ownership', 'tree_path': '2.1.4.1', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'asset_evidence', 'tree_path': '2.1.4.2', +# 'weighting': None, 'fields': [...]}, +# {'section_name': 'asset_background', 'tree_path': '2.1.4.3', +# 'weighting': None, 'fields': [...]}, +# ...] +# all_fields_SAMPLE = [ +# { +# 'form_json_id': 'kxgWTy', +# 'type': 'multilineTextField', +# 'presentation_type': 'text', +# 'title': 'Who in the community uses the asset, or has used it in the past, and who benefits from it?' +# }, +# { +# 'form_json_id': 'wudRxx', +# 'type': 'multilineTextField', +# 'presentation_type': 'text', +# 'title': "Tell us how the asset is currently being used, or how it has been used before, and why it's important to the community" +# } +# ...] +### SQL +# INSERT ALL NEW FIELDS +# for field in all_fields: +# INSERT INTO ASSESSMENT_FIELDS(form_json_id,type,presentation_type,title) +# VALUES(field["form_json_id"],field["type"],field["presentation_type"],field["title"], ) +# reject conflicts +# Funds will be reusing forms so we only need to load new fields +# INSERT SECTIONS +# for section in all_an_sorted_sections_unscored: +# INSERT INTO SECTIONS_TABLE(path_value, section_name, weighting, assessmentORapplication) +# values (etc); +# If this is the lowest level section it will have a "fields" key. +# if "fields" in section: +# for field in fields: +# INSERT INTO SECTIONS_FIELDS(field_id, section_id, display_order) +# values (etc); +# ^ get the section_id from section INSERT above +# NOTE +# There are some instances of duplicate field_ids in the assessment mapping (a single field_id to many assessment field mapping objects), +# suspected because we need to break an application field into multiple assessment elements. +# We should aim to have a 1-1 mapping of fields from application to assessment +# We could handle this locally in the assessment frontend for that type and presentation_type, +# rather than complicate our data model. This may only be true for "add-another" fields (need further investigation). +# This needs further discussion and as it stands, in some cases we store many fields_ids in our mapping +# for one application component +# Changes to remove this behavior on add-another fields to be implemented as part of fs-2500 +# the same is found in reverse (many field_ids to one assessment field mapping object), +# many field ids map (are grouped) to one assessment componenet as a list of field_ids +# suspected because we need to consume multiple fields into one assessment field view. +# At the moment this is handled by giving these grouped ids the same display_order (i.e: all 10) diff --git a/fund_store/config/fund_loader_config/cof/eoi.py b/fund_store/config/fund_loader_config/cof/eoi.py new file mode 100644 index 000000000..10f5d1f9c --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/eoi.py @@ -0,0 +1,208 @@ +# flake8: noqa +import textwrap +from datetime import datetime +from datetime import timezone + +from config.fund_loader_config.cof.eoi_r1_schema import COF_R3_EOI_SCHEMA_CY +from config.fund_loader_config.cof.eoi_r1_schema import COF_R3_EOI_SCHEMA_EN +from config.fund_loader_config.cof.shared import EOI_APPLICATION_GUIDANCE +from config.fund_loader_config.cof.shared import fund_config +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + COF_EOI_BASE_PATH, +) +from config.fund_loader_config.logo import DLUHC_LOGO_PNG +from db.models.fund import FundingType + +COF_FUND_ID = "54c11ec2-0b16-46bb-80d2-f210e47a8791" +COF_EOI_ROUND_ID = "6a47c649-7bac-4583-baed-9c4e7a35c8b3" + +APPLICATION_BASE_PATH_COF_EOI = ".".join([str(COF_EOI_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH_COF_EOI = ".".join([str(COF_EOI_BASE_PATH), str(2)]) + +COF_EOI_OPENS_DATE = datetime(2024, 3, 6, 14, 00, 0, tzinfo=timezone.utc) # 2023-12-06 14:00:00 +COF_EOI_ASSESSMENT_OPENS_DATE = COF_EOI_OPENS_DATE +COF_EOI_DEADLINE_DATE = datetime(2024, 5, 25, 0, 1, 0, tzinfo=timezone.utc) # 2024-05-25 00:01:00 (1min past midnight) +COF_EOI_ASSESSMENT_DEADLINE_DATE = datetime(2124, 3, 6, 12, 0, 0, tzinfo=timezone.utc) # 2124-03-06 12:00:00 + +fund_config = { + "id": COF_FUND_ID, + "name_json": { + "en": "Community Ownership Fund - Expression of Interest", + "cy": "Y Cronfa Perchnogaeth Gymunedol - Mynegi diddordeb", + }, + "title_json": { + "en": "expression of interest in applying for the Community Ownership Fund", + "cy": "Gwneud cais am ddatganiad o ddiddordeb mewn gwneud cais i'r Gronfa Perchnogaeth Gymunedol", + }, + "short_name": "COF-EOI", + "funding_type": FundingType.EOI, + "description_json": fund_config["description_json"], + "welsh_available": True, + "owner_organisation_name": "Department for Levelling Up, Housing and Communities", + "owner_organisation_shortname": "DLUHC", + "owner_organisation_logo_uri": DLUHC_LOGO_PNG, +} + +cof_eoi_sections = [ + { + "section_name": { + "en": "1. Expression of interest", + "cy": "1. Mynegi diddordeb", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_EOI}.1", + "requires_feedback": True, + }, + { + "section_name": { + "en": "1.1 Organisation details", + "cy": "1.1 Manylion y sefydliad", + }, + "form_name_json": { + "en": "organisation-details", + "cy": "manylion-y-sefydliad", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_EOI}.1.1", + }, + { + "section_name": { + "en": "1.2 About your asset", + "cy": "1.2 Ynglŷn â'ch ased", + }, + "form_name_json": { + "en": "about-your-asset", + "cy": "ynglyn-ach-ased", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_EOI}.1.2", + }, + { + "section_name": { + "en": "1.3 Your funding request", + "cy": "1.3 Eich cais am gyllid", + }, + "form_name_json": { + "en": "your-funding-request", + "cy": "eich-cais-am-gyllid", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_EOI}.1.3", + }, + { + "section_name": { + "en": "1.4 Development support provider (not scored)", + "cy": "1.4 Darparwr cymorth datblygu (heb ei sgorio)", + }, + "form_name_json": { + "en": "development-support-provider", + "cy": "darparwr-cymorth-datblygu", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_EOI}.1.4", + }, + { + "section_name": { + "en": "1.5 Declaration", + "cy": "1.5 Datganiad", + }, + "form_name_json": { + "en": "declaration", + "cy": "datganiad", + }, + "tree_path": f"{APPLICATION_BASE_PATH_COF_EOI}.1.5", + }, +] + +round_config_eoi = [ + { + "id": COF_EOI_ROUND_ID, + "fund_id": COF_FUND_ID, + "title_json": {"en": "Expression of interest", "cy": "Mynegi diddordeb"}, + "short_name": "R1", + "opens": COF_EOI_OPENS_DATE, + "assessment_start": COF_EOI_ASSESSMENT_OPENS_DATE, + "deadline": COF_EOI_DEADLINE_DATE, + "application_reminder_sent": False, + "reminder_date": None, + "assessment_deadline": COF_EOI_ASSESSMENT_DEADLINE_DATE, + "prospectus": "https://www.gov.uk/government/publications/community-ownership-fund-prospectus", + "privacy_notice": ( + "https://www.gov.uk/government/publications/community-ownership-fund-" + "privacy-notice/community-ownership-fund-privacy-notice" + ), + "reference_contact_page_over_email": True, + "contact_us_banner_json": { + "en": textwrap.dedent( + """ ++ Visit the My Community website + for information and guidance on applying to Community Ownership Fund. + Fill out the enquiry form + to request advice from My Community. +
++ We cannot provide direct support to applicants outside of this service. +
++ Contact the Department of Levelling Up, Housing and Communities funding team if you need + help with accessing or submitting an application form. +
+ """ + ), + "cy": textwrap.dedent( + """ ++ Ewch i wefan My Community + i gael gwybodaeth ac arweiniad ar wneud cais i'r Gronfa Perchnogaeth Gymunedol. + Llenwch y ffurflen ymholiad + i ofyn am gyngor gan My Community. +
++ Ni allwn ddarparu cymorth uniongyrchol i ymgeiswyr tu hwnt i'r gwasanaeth hwn. +
++ Cysylltwch â thîm cyllid yr Adran Ffyniant Bro, Tai a Chymunedau os oes angen help arnoch i gael at ffurflen gais neu ei chyflwyno. +
+ """ + ), + }, + "contact_email": "COF@communities.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must complete this Expression of Interest (EOI) form if you" + " are interested in applying for the Community Ownership Fund (COF)." + ), + "cy": ( + "Mae'n rhaid i chi gwblhau'r ffurflen Datganiad o Ddiddordeb hon os" + " oes diddordeb gennych mewn gwneud cais i'r Gronfa Perchnogaeth Gymunedol." + ), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElURUY1WkhaS0NFMlZVQUhYQ1NaN0E4RjlQMC4u" + ), + "project_name_field_id": "SMRWjl", + "application_guidance_json": EOI_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://www.gov.uk/government/publications/community-ownership-fund-round-3-application-form" + "-assessment-criteria-guidance" + ), + "all_uploaded_documents_section_available": False, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": True, + "feedback_survey_config": { + "has_feedback_survey": False, + "has_section_feedback": True, + "is_feedback_survey_optional": False, + "is_section_feedback_optional": True, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": {"en": COF_R3_EOI_SCHEMA_EN, "cy": COF_R3_EOI_SCHEMA_CY}, + } +] diff --git a/fund_store/config/fund_loader_config/cof/eoi_r1_schema.py b/fund_store/config/fund_loader_config/cof/eoi_r1_schema.py new file mode 100644 index 000000000..72fc98630 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/eoi_r1_schema.py @@ -0,0 +1,419 @@ +from fsd_utils import Decision + +COF_SECURE_MATCH_FUNDING_CAVEAT_EN = ( + "Make progress in securing match funding: COF will contribute up to 80% of the" + " capital costs you require, and you must raise at least 20% from other sources." + " You do not need to have secured all your match funding by the time you apply, but" + " we will ask you to set out your total costs, funding already secured, and plans" + " to raise any additional funding. You must use COF funding within 12 months, so" + " you must be able to show that you've made good progress to secure the remaining" + " match funding. This is so that we're confident you can draw down this funding" + " within this timeframe." +) +COF_SECURE_MATCH_FUNDING_CAVEAT_CY = ( + "Gwneud cynnydd i sicrhau arian cyfatebol: Bydd y Gronfa Perchnogaeth Gymunedol yn cyfrannu hyd at 80% o'r costau " + "cyfalaf sydd eu hangen arnoch, ac mae'n rhaid i chi godi o leiaf 20% o ffynonellau eraill. Nid oes angen i chi " + "fod wedi sicrhau eich holl arian cyfatebol erbyn i chi wneud cais, ond byddwn yn gofyn i chi nodi cyfanswm eich " + "costau, y cyllid rydych eisoes wedi'i sicrhau, a chynlluniau i godi unrhyw gyllid ychwanegol. Mae'n rhaid i chi " + "ddefnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol o fewn 12 mis, felly mae'n rhaid i chi allu dangos eich bod " + "wedi gwneud cynnydd da i sicrhau'r arian cyfatebol sy'n weddill. Diben hyn yw rhoi'r hyder i ni y gallwch " + "ddefnyddio'r cyllid hwn o fewn yr amserlen hon." +) + + +COF_PLANNING_PERMISSION_IF_NEEDED_CAVEAT_EN = ( + "Get planning permission, if needed: When you apply, you must be able to show that you have secured or have made" + " good progress in securing planning permission, if needed (and building warrants, if required). This is so that" + " we're confident that COF funding will be used within the 12 month timeframe." +) +COF_PLANNING_PERMISSION_IF_NEEDED_CAVEAT_CY = ( + "Sicrhewch ganiatâd cynllunio, os oes angen: Pan fyddwch yn gwneud cais, rhaid i chi allu dangos eich bod wedi " + "sicrhau caniatâd cynllunio os oes angen (a gwarantau adeiladu, os oes angen), neu'ch bod wedi gwneud cynnydd da " + "yn hyn o beth. Diben hyn yw rhoi'r hyder i ni y caiff cyllid o'r Gronfa Perchnogaeth Gymunedol ei ddefnyddio o " + "fewn y cyfnod o 12 mis." +) + + +COF_PLANNING_PERMISSION_CAVEAT_EN = ( + "Get planning permission: When you apply, you must be able to show that you have secured or have made good progress" + " in securing planning permission (and building warrants, if required). This is so that we're confident that COF" + " funding will be used within the 12 month timeframe." +) +COF_PLANNING_PERMISSION_CAVEAT_CY = ( + "Sicrhewch ganiatâd cynllunio: Pan fyddwch yn gwneud cais, rhaid i chi allu dangos eich bod wedi sicrhau caniatâd " + "cynllunio (a gwarantau adeiladu, os oes angen), neu'ch bod wedi gwneud cynnydd da yn hyn o beth. Diben hyn yw " + "rhoi'r hyder i ni y caiff cyllid o'r Gronfa Perchnogaeth Gymunedol ei ddefnyddio o fewn y cyfnod o 12 mis." +) + + +COF_R3_EOI_SCHEMA_EN = { + "uYiLsv": [ + { + "answerValue": "not-yet-incorporated", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Incorporate your organisation: You must have incorporated your" + " organisation by the time you submit a full application. If you remain" + " unincorporated, your application will be ineligible." + ), + }, + ], + "NcQSbU": [ + { + "answerValue": True, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "eEaDGz": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "zurxox": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "lLQmNb": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "fBhSNc": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "XuAyrs": [ + { + "answerValue": "Yes, a town, parish or community council", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Understand the rules on acquiring assets from town, parish or" + " community councils: We cannot fund you to acquire a publicly owned" + " asset if this involves transferring responsibility for delivering" + " statutory services (services paid for by tax payers) from the public" + " authority to your organisation. You should only apply to acquire an" + " asset from a town, parish or community council if you do not plan to" + " deliver statutory services." + ), + }, + { + "answerValue": "Yes, another type of public authority", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Understand the rules on acquiring public sector assets: COF funding" + " can only be used for renovation and refurbishment costs once a" + " publicly owned asset has been transferred to you. We cannot fund" + " capital receipts, unless the costs incurred in transferring the asset" + " to you are nominal (very small and far below the real value).In your" + " application, you should show that you are not asking COF to fund a" + " capital receipt to a public authority (for example, by sharing a" + " letter confirming the authority is willing/has already agreed a" + " long-term lease and no capital receipt is involved).We also cannot" + " fund you to acquire a publicly owned asset if this involves" + " transferring responsibility for delivering statutory services" + " (services paid for by tax payers) from the public authority to your" + " organisation" + ), + }, + ], + "foQgiy": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "BykoQQ": [ + { + "answerValue": ["Not sure"], + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Make progress in securing match funding: COF will contribute up to 80%" + " of the capital costs you require, and you must raise at least 20%" + " from other sources.You do not need to have secured all your match" + " funding by the time you apply, but we will ask you to set out your" + " total costs, funding already secured, and plans to raise any" + " additional funding.You must use COF funding within 12 months, so you" + " must be able to show that you've made good progress to secure the" + " remaining match funding. This is so that we're confident you can draw" + " down this funding within this timeframe." + ), + }, + ], + "eOWKoO": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "oblxxv": [ + { + "answerValue": False, + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Consider requesting revenue funding: We encourage all organisations" + " to apply for revenue funding to help cover the initial running costs" + " of your project. When you apply, you'll need to show us how you" + " plan to use any revenue funding." + " See [Section 9 of the COF prospectus for more" + " guidance](https://www.gov.uk/government/publications/community-" + "ownership-fund-prospectus/community-ownership-fund-prospectus--3#funding-available)." + ), + }, + ], + "kWRuac": [ + { + "answerValue": "Not yet approached any funders", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_EN, + }, + { + "answerValue": "Approached some funders but not yet secured", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_EN, + }, + { + "answerValue": "Approached all funders but not yet secured", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_EN, + }, + { + "answerValue": "Secured some match funding", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_EN, + }, + ], + "yZxdeJ": [ + { + "answerValue": True, + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Understand the rules on housing: We will not provide funding if your" + " project's main purpose is to purchase or develop housing assets," + " including social housing. However, you can include housing elements" + " in your project where these are only a small part of supporting the" + " overall financial sustainability of the asset in community ownership." + ), + } + ], + "UORyaF": [ + { + "answerValue": "Not sure", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_PLANNING_PERMISSION_IF_NEEDED_CAVEAT_EN, + } + ], + "jICagT": [ + { + "answerValue": "Not yet started", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_PLANNING_PERMISSION_CAVEAT_EN, + }, + { + "answerValue": "Early stage", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_PLANNING_PERMISSION_CAVEAT_EN, + }, + ], + "fZAMFv": [ + { + "operator": ">", + "compareValue": 2000000, # 2 million + "result": Decision.FAIL, + "caveat": None, + } + ], +} +COF_R3_EOI_SCHEMA_CY = { + "uYiLsv": [ + { + "answerValue": "Ddim yn gorfforedig eto", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Dylech gorffori eich sefydliad: Mae'n rhaid eich bod wedi corffori eich sefydliad erbyn eich bod yn " + "cyflwyno cais llawn. Os byddwch yn anghorfforedig o hyd, ni fydd eich cais yn gymwys." + ), + }, + ], + "NcQSbU": [ + { + "answerValue": True, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "eEaDGz": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "zurxox": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "lLQmNb": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "fBhSNc": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "XuAyrs": [ + { + "answerValue": "Ydy, cyngor tref, plwyf neu gymuned", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Dylech ddeall y rheolau ynglŷn â chaffael asedau gan gynghorau tref, plwyf neu gymuned: Ni allwn " + "eich ariannu i gaffael ased dan berchnogaeth gyhoeddus os yw'n golygu trosglwyddo cyfrifoldeb am " + "ddarparu gwasanaethau statudol (gwasanaethau y mae trethdalwyr yn talu amdanynt) o'r awdurdod " + "cyhoeddus i'ch sefydliad. Dim ond os nad ydych yn bwriadu darparu gwasanaethau statudol y dylech " + "wneud cais i gaffael ased gan gyngor tref, plwyf neu gymuned." + ), + }, + { + "answerValue": "Ydy, math arall o awdurdod cyhoeddus", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Dylech ddeall y rheolau ynglŷn â chaffael asedau'r sector cyhoeddus: Dim ond ar ôl trosglwyddo ased " + "sydd dan berchnogaeth gyhoeddus i chi y gellir defnyddio cyllid o'r Gronfa Perchnogaeth Gymunedol ar " + "gyfer costau adnewyddu ac ailwampio. Ni allwn ariannu derbyniad cyfalaf, oni bai bod y costau yr aed " + "iddynt wrth drosglwyddo'r ased i chi yn nominal (bach iawn ac yn llawer is na'r gwerth gwirioneddol). " + "Yn eich cais, dylech ddangos nad ydych yn gofyn i'r Gronfa Perchnogaeth Gymunedol ariannu derbyniad " + "cyfalaf i awdurdod cyhoeddus (er enghraifft drwy rannu llythyr yn cadarnhau bod yr awdurdod yn fodlon " + "ar/eisoes wedi cytuno i les hirdymor ac nad oes derbyniad cyfalaf yn gysylltiedig). Ni allwn ychwaith " + "eich ariannu i gaffael ased dan berchnogaeth gyhoeddus os yw'n golygu trosglwyddo cyfrifoldeb am " + "ddarparu gwasanaethau statudol (gwasanaethau y mae trethdalwyr yn talu amdanynt) o'r awdurdod " + "cyhoeddus i'ch sefydliad." + ), + }, + ], + "foQgiy": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "BykoQQ": [ + { + "answerValue": ["none"], + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Gwneud cynnydd i sicrhau arian cyfatebol: Bydd y Gronfa Perchnogaeth Gymunedol yn cyfrannu hyd at " + "80% o'r costau cyfalaf sydd eu hangen arnoch, ac mae'n rhaid i chi godi o leiaf 20% o ffynonellau " + "eraill. Nid oes angen i chi fod wedi sicrhau eich holl arian cyfatebol erbyn i chi wneud cais, " + "ond byddwn yn gofyn i chi nodi cyfanswm eich costau, y cyllid rydych eisoes wedi'i sicrhau, " + "a chynlluniau i godi unrhyw gyllid ychwanegol. Mae'n rhaid i chi ddefnyddio cyllid o'r Gronfa " + "Perchnogaeth Gymunedol o fewn 12 mis, felly mae'n rhaid i chi allu dangos eich bod wedi gwneud " + "cynnydd da i sicrhau'r arian cyfatebol sy'n weddill. Diben hyn yw rhoi'r hyder i ni y gallwch " + "ddefnyddio'r cyllid hwn o fewn yr amserlen hon." + ), + }, + ], + "eOWKoO": [ + { + "answerValue": False, + "result": Decision.FAIL, + "caveat": None, + }, + ], + "oblxxv": [ + { + "answerValue": False, + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Ystyriwch wneud cais am gyllid refeniw: Rydym yn annog pob sefydliad i wneud cais am gyllid refeniw " + "er mwyn helpu i dalu costau rhedeg cychwynnol eich prosiect. Pan fyddwch yn gwneud cais, bydd angen " + "i chi ddangos i ni sut rydych yn bwriadu defnyddio unrhyw gyllid refeniw. [Gweler Adran 9 o " + "brosbectws y Gronfa Perchnogaeth Gymunedol am ragor o ganllawiau.](" + "https://www.gov.uk/government/publications/community-ownership-fund-prospectus/community-ownership" + "-fund-prospectus--3#funding-available)" + ), + }, + ], + "kWRuac": [ + { + "answerValue": "Heb gysylltu ag unrhyw gyllidwyr eto", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_CY, + }, + { + "answerValue": "Wedi cysylltu â rhai cyllidwyr ond heb sicrhau cyllid eto", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_CY, + }, + { + "answerValue": "Wedi cysylltu â'r holl gyllidwyr ond heb sicrhau cyllid eto", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_CY, + }, + { + "answerValue": "Wedi sicrhau rhywfaint o arian cyfatebol", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_SECURE_MATCH_FUNDING_CAVEAT_CY, + }, + ], + "yZxdeJ": [ + { + "answerValue": True, + "result": Decision.PASS_WITH_CAVEATS, + "caveat": ( + "Dylech ddeall y rheolau ynglyn â thai: Ni fyddwn yn darparu cyllid os mai prif ddiben eich prosiect " + "yw prynu neu ddatblygu asedau tai, gan gynnwys tai cymdeithasol. Fodd bynnag, gallwch gynnwys " + "elfennau tai yn eich prosiect os mai dim ond rhan fach o gefnogi cynaliadwyedd ariannol gyffredinol " + "yr ased dan berchnogaeth gymunedol yw'r rhain." + ), + } + ], + "UORyaF": [ + { + "answerValue": "Ddim yn siŵr", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_PLANNING_PERMISSION_IF_NEEDED_CAVEAT_CY, + } + ], + "jICagT": [ + { + "answerValue": "Heb ddechrau eto", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_PLANNING_PERMISSION_CAVEAT_CY, + }, + { + "answerValue": "Cam cynnar", + "result": Decision.PASS_WITH_CAVEATS, + "caveat": COF_PLANNING_PERMISSION_CAVEAT_CY, + }, + ], + "fZAMFv": [ + { + "operator": ">", + "compareValue": 2000000, # 2 million + "result": Decision.FAIL, + "caveat": None, + } + ], +} diff --git a/fund_store/config/fund_loader_config/cof/shared.py b/fund_store/config/fund_loader_config/cof/shared.py new file mode 100644 index 000000000..1fc2ac806 --- /dev/null +++ b/fund_store/config/fund_loader_config/cof/shared.py @@ -0,0 +1,64 @@ +from config.fund_loader_config.logo import DLUHC_LOGO_PNG +from db.models.fund import FundingType + +fund_config = { + "id": "47aef2f5-3fcb-4d45-acb5-f0152b5f03c4", + "name_json": { + "en": "Community Ownership Fund", + "cy": "Y Cronfa Perchnogaeth Gymunedol", + }, + "title_json": { + "en": "funding to save an asset in your community", + "cy": "gyllid i achub ased yn eich cymuned", + }, + "short_name": "COF", + "funding_type": FundingType.COMPETITIVE, + "description_json": { + "en": ( + "The Community Ownership Fund is a £150 million fund over 4 years" + " to support community groups across England, Wales, Scotland and" + " Northern Ireland to take ownership of assets which are at risk" + " of being lost to the community." + ), + "cy": ( # TODO: Provide welsh translation + "The Community Ownership Fund is a £150 million fund over 4 years" + " to support community groups across England, Wales, Scotland and" + " Northern Ireland to take ownership of assets which are at risk" + " of being lost to the community." + ), + }, + "welsh_available": True, + "owner_organisation_name": "Department for Levelling Up, Housing and Communities", + "owner_organisation_shortname": "DLUHC", + "owner_organisation_logo_uri": DLUHC_LOGO_PNG, +} + +COF_APPLICATION_GUIDANCE = { + "en": ( + "You can preview the full list of" + " application questions.
We'll also ask you to upload" + " a business plan to support the answers you've given us in the management case" + " section.
" + ), + "cy": ( + "Gallwch gael cip ymlaen llaw o restr lawn" + " cwestiynau'r cais.
Byddwn hefyd yn gofyn i chi " + "lanlwytho cynllun busnes i ategu'r atebion rydych wedi'u rhoi i ni yn" + " yr adran achos rheoli.
" + ), +} + +EOI_APPLICATION_GUIDANCE = { + "en": ( + "You can preview the full list of" + " application questions.
" + ), + "cy": ( + "Gallwch gael rhagolwg o'r rhestr lawn o" + " gwestiynau yn y cais.
" + ), +} diff --git a/fund_store/config/fund_loader_config/common_fund_config/fund_base_tree_paths.py b/fund_store/config/fund_loader_config/common_fund_config/fund_base_tree_paths.py new file mode 100644 index 000000000..dfb07179d --- /dev/null +++ b/fund_store/config/fund_loader_config/common_fund_config/fund_base_tree_paths.py @@ -0,0 +1,17 @@ +# Should increment for each new round, anything that shares the same base path will also share +# the child tree path config. +# +COF_R2_W2_BASE_PATH = 1 +COF_R2_W3_BASE_PATH = 1 +COF_R3_W1_BASE_PATH = 2 +NSTF_R2_BASE_PATH = 3 +COF_R3_W2_BASE_PATH = 4 +CYP_R1_BASE_PATH = 5 +DPI_R2_BASE_PATH = 6 +COF_R3_W3_BASE_PATH = 7 +COF_EOI_BASE_PATH = 8 +COF_R4_W1_BASE_PATH = 9 +HSRA_BASE_PATH = 10 +COF_R4_W2_BASE_PATH = 11 + +FAB_BASE_PATH = 0 diff --git a/fund_store/config/fund_loader_config/cyp/cyp_r1.py b/fund_store/config/fund_loader_config/cyp/cyp_r1.py new file mode 100644 index 000000000..7891efed0 --- /dev/null +++ b/fund_store/config/fund_loader_config/cyp/cyp_r1.py @@ -0,0 +1,195 @@ +from datetime import datetime, timezone + +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + CYP_R1_BASE_PATH, +) +from config.fund_loader_config.logo import DLUHC_LOGO_PNG +from db.models.fund import FundingType + +CYP_FUND_ID = "1baa0f68-4e0a-4b02-9dfe-b5646f089e65" +CYP_ROUND_1_ID = "888aae3d-7e2c-4523-b9c1-95952b3d1644" +APPLICATION_BASE_PATH = ".".join([str(CYP_R1_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH = ".".join([str(CYP_R1_BASE_PATH), str(2)]) +CYP_R1_OPENS_DATE = datetime(2023, 9, 27, 10, 0, 0, tzinfo=timezone.utc) # 2023-09-27 10:00:00 +CYP_R1_DEADLINE_DATE = datetime(2023, 11, 1, 11, 59, 0, tzinfo=timezone.utc) # 2023-11-1 11:59:00 +CYP_R1_ASSESSMENT_DEADLINE_DATE = datetime(2023, 12, 24, 12, 0, 0, tzinfo=timezone.utc) # 2023-12-24 12:00:00 + +CYP_PROSPECTS_LINK = "https://www.gov.uk/government/publications/the-children-and-young-peoples-resettlement-fund-prospectus/the-children-and-young-peoples-resettlement-fund-prospectus" # noqa +CYP_APPLICATION_GUIDANCE = { + "en": ( + "Read the fund's prospectus" + " before you apply.
You can preview the full list of application" + " questions.
" + ) +} + +r1_application_sections = [ + { + "section_name": {"en": "Before you start", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.1", + }, + { + "section_name": {"en": "1.1 Name your application", "cy": ""}, + "form_name_json": {"en": "name-your-application-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.1.1", + }, + { + "section_name": {"en": "2. About your organisation", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2", + "requires_feedback": True, + }, + { + "section_name": {"en": "2.1 About your organisation", "cy": ""}, + "form_name_json": {"en": "about-your-organisation-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.1", + }, + { + "section_name": {"en": "2.2 Applicant information", "cy": ""}, + "form_name_json": {"en": "applicant-information-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.2", + }, + { + "section_name": {"en": "3. Your skills and experience", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3", + "requires_feedback": True, + }, + { + "section_name": {"en": "3.1 Your skills and experience", "cy": ""}, + "form_name_json": {"en": "skills-and-experience-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.1", + }, + { + "section_name": {"en": "4. Your project", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4", + "requires_feedback": True, + }, + { + "section_name": {"en": "4.1 Outputs and outcomes", "cy": ""}, + "form_name_json": {"en": "outputs-and-outcomes-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.1", + }, + { + "section_name": {"en": "4.2 Existing work", "cy": ""}, + "form_name_json": {"en": "existing-work-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.2", + }, + { + "section_name": {"en": "4.3 Project milestones", "cy": ""}, + "form_name_json": {"en": "project-milestones-cyp", "cy": " "}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.3", + }, + { + "section_name": {"en": "4.4 Objectives and activities", "cy": ""}, + "form_name_json": {"en": "objectives-and-activities-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.4", + }, + { + "section_name": {"en": "4.5 Location of activities", "cy": ""}, + "form_name_json": {"en": "location-of-activities-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.5", + }, + { + "section_name": {"en": "4.6 Working with fund beneficiaries", "cy": ""}, + "form_name_json": {"en": "working-with-fund-beneficiaries-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.6", + }, + { + "section_name": {"en": "5. Risk and deliverability", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.5", + "requires_feedback": True, + }, + { + "section_name": {"en": "5.1 Risk and deliverability", "cy": ""}, + "form_name_json": {"en": "risk-and-deliverability-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.5.1", + }, + { + "section_name": {"en": "6. Value for money", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.6", + "requires_feedback": True, + }, + { + "section_name": {"en": "6.1 Value for money", "cy": ""}, + "form_name_json": {"en": "value-for-money-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.6.1", + }, + { + "section_name": {"en": "7. Declarations", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.7", + }, + { + "section_name": {"en": "7.1 Declarations", "cy": ""}, + "form_name_json": {"en": "declarations-cyp", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.7.1", + }, +] + +fund_config = { + "id": CYP_FUND_ID, + "name_json": { + "en": "The Children and Young People's Resettlement Fund", + "cy": "", + }, + "title_json": { + "en": ( + "funding to support children and young people on pathways to the UK from Ukraine, Hong Kong and Afghanistan" + ), + "cy": "", + }, + "short_name": "CYP", + "funding_type": FundingType.COMPETITIVE, + "description_json": {"en": "", "cy": ""}, + "welsh_available": False, + "owner_organisation_name": "Department for Levelling Up, Housing and Communities", + "owner_organisation_shortname": "DLUHC", + "owner_organisation_logo_uri": DLUHC_LOGO_PNG, +} + +round_config = [ + { + "id": CYP_ROUND_1_ID, + "fund_id": CYP_FUND_ID, + "title_json": {"en": "Round 1", "cy": "Rownd 1"}, + "short_name": "R1", + "opens": CYP_R1_OPENS_DATE, + "assessment_start": None, + "deadline": CYP_R1_DEADLINE_DATE, + "application_reminder_sent": True, + "reminder_date": None, + "assessment_deadline": CYP_R1_ASSESSMENT_DEADLINE_DATE, + "prospectus": CYP_PROSPECTS_LINK, + "privacy_notice": "https://www.gov.uk/guidance/the-children-and-young-peoples-resettlement-fund-privacy-notice", + "reference_contact_page_over_email": False, + "contact_us_banner_json": {"en": "", "cy": ""}, + "contact_email": "cyprfund@levellingup.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": None, + "feedback_link": "", + "project_name_field_id": "bsUoNG", + "application_guidance_json": CYP_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://www.gov.uk/government/publications/" + "the-children-and-young-peoples-resettlement-" + "fund-prospectus/the-children-and-young-peoples-" + "resettlement-fund-prospectus#scoring-criteria" + ), + "all_uploaded_documents_section_available": False, + "application_fields_download_available": False, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": True, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": True, + "has_section_feedback": True, + "is_feedback_survey_optional": False, + "is_section_feedback_optional": False, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + } +] diff --git a/fund_store/config/fund_loader_config/digital_planning/dpi_r2.py b/fund_store/config/fund_loader_config/digital_planning/dpi_r2.py new file mode 100644 index 000000000..a1198fb09 --- /dev/null +++ b/fund_store/config/fund_loader_config/digital_planning/dpi_r2.py @@ -0,0 +1,169 @@ +from datetime import datetime, timezone + +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + DPI_R2_BASE_PATH, +) +from config.fund_loader_config.logo import DLUHC_LOGO_PNG +from db.models.fund import FundingType + +DPI_FUND_ID = "f493d512-5eb4-11ee-8c99-0242ac120002" +DPI_ROUND_2_ID = "0059aad4-5eb5-11ee-8c99-0242ac120002" +APPLICATION_BASE_PATH = ".".join([str(DPI_R2_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH = ".".join([str(DPI_R2_BASE_PATH), str(2)]) +DPI_R2_OPENS_DATE = datetime(2023, 10, 17, 9, 30, 0, tzinfo=timezone.utc) # 2023-10-17 10:00:00 +DPI_R2_DEADLINE_DATE = datetime(2023, 12, 1, 17, 0, 0, tzinfo=timezone.utc) # 2023-12-1 11:59:00 +DPI_R2_ASSESSMENT_DEADLINE_DATE = datetime(2024, 1, 31, 12, 0, 0, tzinfo=timezone.utc) # 2023-01-31 12:00:00 + +DPI_PROSPECTS_LINK = "https://www.localdigital.gov.uk/digital-planning/funding/digital-planning-programme-funding-2023" # noqa +DPI_PRIVACY_NOTICE = "https://www.gov.uk/guidance/digital-planning-improvement-fund-privacy-notice" +DPI_APPLICATION_GUIDANCE = { + "en": ( + "Read the fund's prospectus" + " before you apply.
You can preview the full list of application" + " questions.
" + ) +} + +r2_application_sections = [ + { + "section_name": {"en": "1. Before you start", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.1", + }, + { + "section_name": {"en": "1.1 Name your application", "cy": ""}, + "form_name_json": {"en": "name-your-application", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.1.1", + }, + { + "section_name": {"en": "2. About your organisation", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2", + "requires_feedback": True, + }, + { + "section_name": {"en": "2.1 Organisation information", "cy": ""}, + "form_name_json": {"en": "organisation-information-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.1", + }, + { + "section_name": {"en": "3. Your skills and experience", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3", + "weighting": 50, + "requires_feedback": True, + }, + { + "section_name": {"en": "3.1 Your skills and experience", "cy": ""}, + "form_name_json": {"en": "your-skills-and-experience-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.1", + }, + { + "section_name": {"en": "3.2 Roles and recruitment", "cy": ""}, + "form_name_json": {"en": "roles-and-recruitment-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.2", + }, + { + "section_name": {"en": "4. About your project", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4", + "weighting": 50, + "requires_feedback": True, + }, + { + "section_name": {"en": "4.1 Engaging the ODP community", "cy": ""}, + "form_name_json": {"en": "engaging-the-odp-community-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.1", + }, + { + "section_name": {"en": "4.2 Engaging the organisation", "cy": ""}, + "form_name_json": {"en": "engaging-the-organisation-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.2", + }, + { + "section_name": {"en": "4.3 Dataset information", "cy": ""}, + "form_name_json": {"en": "dataset-information-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.3", + }, + { + "section_name": {"en": "5. Future work", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.5", + "requires_feedback": True, + }, + { + "section_name": {"en": "5.1 Future work", "cy": ""}, + "form_name_json": {"en": "future-work-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.5.1", + }, + { + "section_name": {"en": "6. Declarations", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.6", + }, + { + "section_name": {"en": "6.1 Declarations", "cy": ""}, + "form_name_json": {"en": "declarations-dpi", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.6.1", + }, +] + +fund_config = { + "id": DPI_FUND_ID, + "name_json": { + "en": "Digital Planning Improvement Fund", + "cy": "", + }, + "title_json": { + "en": "funding to begin your digital planning improvement journey", + "cy": "", + }, + "short_name": "DPIF", + "funding_type": FundingType.COMPETITIVE, + "description_json": {"en": "", "cy": ""}, + "welsh_available": False, + "owner_organisation_name": "Department for Levelling Up, Housing and Communities", + "owner_organisation_shortname": "DLUHC", + "owner_organisation_logo_uri": DLUHC_LOGO_PNG, +} + +round_config = [ + { + "id": DPI_ROUND_2_ID, + "fund_id": DPI_FUND_ID, + "title_json": {"en": "Round 2", "cy": ""}, + "short_name": "R2", + "opens": DPI_R2_OPENS_DATE, + "assessment_start": None, + "deadline": DPI_R2_DEADLINE_DATE, + "application_reminder_sent": True, + "reminder_date": None, + "assessment_deadline": DPI_R2_ASSESSMENT_DEADLINE_DATE, + "prospectus": DPI_PROSPECTS_LINK, + "privacy_notice": DPI_PRIVACY_NOTICE, + "reference_contact_page_over_email": False, + "contact_us_banner_json": {"en": "", "cy": ""}, + "contact_email": "digitalplanningteam@levellingup.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": None, + "feedback_link": "", + "project_name_field_id": "JAAhRP", + "application_guidance_json": DPI_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://docs.google.com/document/d/1cF5eKphoBWEUe0Zv5HBwv0R3n1svCk16kUFRJhKnIQY" + "/edit#heading=h.b0vrhm5gih2k" + ), + "all_uploaded_documents_section_available": False, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": True, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": True, + "has_section_feedback": True, + "is_feedback_survey_optional": True, + "is_section_feedback_optional": True, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + } +] diff --git a/fund_store/config/fund_loader_config/hsra/hsra.py b/fund_store/config/fund_loader_config/hsra/hsra.py new file mode 100644 index 000000000..dc218292b --- /dev/null +++ b/fund_store/config/fund_loader_config/hsra/hsra.py @@ -0,0 +1,172 @@ +from datetime import datetime, timezone + +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + HSRA_BASE_PATH, +) +from config.fund_loader_config.hsra.shared import HSRA_APPLICATION_GUIDANCE +from config.fund_loader_config.logo import DLUHC_LOGO_PNG +from db.models.fund import FundingType + +HSRA_FUND_ID = "1e4bd8b0-b399-466d-bbd1-572171bbc7bd" +HSRA_ROUND_ID = "50062ff6-e696-474d-a560-4d9af784e6e5" + +APPLICATION_BASE_PATH_HSRA = ".".join([str(HSRA_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH_HSRA = ".".join([str(HSRA_BASE_PATH), str(2)]) + +# TODO Dates are still being debated and are likely to change +HSRA_OPENS_DATE = datetime(2024, 6, 27, 11, 00, 0, tzinfo=timezone.utc) # 2024-06-27 11:00:00 +HSRA_START_DATE = datetime(2024, 6, 27, 11, 00, 0, tzinfo=timezone.utc) # 2024-06-27 11:00:00 +# TODO The bellow dates are likely to change when the fund is live +HSRA_SEND_REMINDER_DATE = datetime(2025, 1, 27, 11, 59, 0, tzinfo=timezone.utc) # 2025-1-27 11:59:00 +HSRA_DEADLINE_DATE = datetime(2025, 1, 29, 11, 00, 0, tzinfo=timezone.utc) # 2025-01-29 11:00:00 +HSRA_ASSESSMENT_DEADLINE_DATE = datetime(2024, 6, 23, 12, 0, 0, tzinfo=timezone.utc) # 2024-06-23 12:00:00 + +hsra_sections = [ + { + "section_name": {"en": "1. Application name", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.1", + }, + { + "section_name": {"en": "Name your application", "cy": ""}, + "form_name_json": {"en": "name-your-application-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.1.1", + }, + { + "section_name": {"en": "2. About your organisation", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.2", + }, + { + "section_name": {"en": "Organisation information", "cy": ""}, + "form_name_json": {"en": "organisation-information-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.2.1", + }, + { + "section_name": {"en": "Applicant information", "cy": ""}, + "form_name_json": {"en": "applicant-information-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.2.2", + }, + { + "section_name": {"en": "Joint applicant", "cy": ""}, + "form_name_json": {"en": "joint-applicant-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.2.3", + }, + { + "section_name": {"en": "3. About your project", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.3", + }, + { + "section_name": {"en": "Vacant property details", "cy": ""}, + "form_name_json": {"en": "vacant-property-details-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.3.1", + }, + { + "section_name": {"en": "Designated area details", "cy": ""}, + "form_name_json": {"en": "designated-area-details-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.3.2", + }, + { + "section_name": {"en": "Project milestones", "cy": ""}, + "form_name_json": {"en": "milestones-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.3.3", + }, + { + "section_name": {"en": "4. Project costs", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.4", + }, + { + "section_name": {"en": "Total expected cost", "cy": ""}, + "form_name_json": {"en": "total-expected-cost-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.4.1", + }, + { + "section_name": {"en": "Refurbishment costs", "cy": ""}, + "form_name_json": {"en": "refurbishment-costs-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.4.2", + }, + { + "section_name": {"en": "Other costs", "cy": ""}, + "form_name_json": {"en": "other-costs-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.4.3", + }, + { + "section_name": {"en": "5. Declaration", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.5", + }, + { + "section_name": {"en": "Declaration", "cy": ""}, + "form_name_json": {"en": "declaration-hsra", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH_HSRA}.5.1", + }, +] + +fund_config = { + "id": HSRA_FUND_ID, + "name_json": { + "en": "High Street Rental Auctions Fund", + "cy": "", + }, + "title_json": { + "en": "funding to cover the cost of delivering a high street rental auction", + "cy": "", + }, + "short_name": "HSRA", + "funding_type": FundingType.COMPETITIVE, + "description_json": {"en": "", "cy": ""}, + "welsh_available": False, + "owner_organisation_name": "Department for Levelling Up, Housing and Communities", + "owner_organisation_shortname": "DLUHC", + "owner_organisation_logo_uri": DLUHC_LOGO_PNG, +} + +round_config = [ + { + "id": HSRA_ROUND_ID, + "fund_id": HSRA_FUND_ID, + "title_json": {"en": "Round 1", "cy": "Rownd 1"}, + "short_name": "R1", + "opens": HSRA_OPENS_DATE, + "assessment_start": HSRA_START_DATE, + "deadline": HSRA_DEADLINE_DATE, + "application_reminder_sent": False, + "reminder_date": HSRA_SEND_REMINDER_DATE, + "assessment_deadline": HSRA_ASSESSMENT_DEADLINE_DATE, + "prospectus": "", # TODO needs to be added + "privacy_notice": "", + "reference_contact_page_over_email": False, + "contact_us_banner_json": {"en": "", "cy": ""}, + "contact_email": "HighStreetRentalAuctions@levellingup.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": { + "en": ( + "You must have received an invitation to apply. If we did not invite you," + " first ' + " express your interest in the fund." + ), # TODO Need to guidance link + "cy": (""), + }, + "feedback_link": ( + "https://forms.office.com/Pages/ResponsePage.aspx?id=" + "EGg0v32c3kOociSi7zmVqFJBHpeOL2tNnpiwpdL2iElURUY1WkhaS0NFMlZVQUhYQ1NaN0E4RjlQMC4u" + ), + "project_name_field_id": "qbBtUh", + "application_guidance_json": HSRA_APPLICATION_GUIDANCE, + "guidance_url": "", # TODO add guidance link + "all_uploaded_documents_section_available": True, + "application_fields_download_available": True, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": True, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": False, + "has_section_feedback": False, + "is_feedback_survey_optional": False, + "is_section_feedback_optional": False, + }, + "eligibility_config": {"has_eligibility": True}, + "eoi_decision_schema": None, + } +] diff --git a/fund_store/config/fund_loader_config/hsra/shared.py b/fund_store/config/fund_loader_config/hsra/shared.py new file mode 100644 index 000000000..382d0d6c7 --- /dev/null +++ b/fund_store/config/fund_loader_config/hsra/shared.py @@ -0,0 +1,9 @@ +HSRA_APPLICATION_GUIDANCE = { + "en": ( + "You can preview the full list of" + " application questions.
We'll also ask you to upload" + " a business plan to support the answers you've given us in the management case" + " section.
" + ) +} diff --git a/fund_store/config/fund_loader_config/logo.py b/fund_store/config/fund_loader_config/logo.py new file mode 100644 index 000000000..7f8c10450 --- /dev/null +++ b/fund_store/config/fund_loader_config/logo.py @@ -0,0 +1 @@ +DLUHC_LOGO_PNG = "data:image/png;base64, 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JEmSJEmSVDUMQEuSJEmSJEmSJEmSJEmSJEmqGgagJUmSJEmSJEmSJEmSJEmSJFUNA9CSJEmSJEmSJEmSJEmSJEmSqoYBaEmSJEmSJEmSJEmSJEmSJElVwwC0JEmSJEmSJEmSJEmSJEmSpKphAFqSJEmSJEmSJEmSJEmSJElS1TAALUmSJEmSJEmSJEmSJEmSJKlqGICWJEmSJEmSJEmSJEmSJEmSVDUMQEuSJEmSJEmSJEmSJEmSJEmqGgagJUmSJEmSJEmSJEmSJEmSJFUNA9CSJEmSJEmSJEmSJEmSJEmSqoYBaEmSJEmSJEmSJEmSJEmSJElVwwC0JEmSJEmSJEmSJEmSJEmSpKphAFqSJEmSJEmSJEmSJEmSJElS1TAALUmSJEmSJEmSJEmSJEmSJKlqGICWJEmSJEmSJEmSJEmSJEmSVDUMQEuSJEmSJEmSJEmSJEmSJEmqGgagJUmSJEmSJEmSJEmSJEmSJFUNA9CSJEmSJEmSJEmSJEmSJEmSqoYBaEmSJEmSJEmSJEmSJEmSJElVwwC0JEmSJEmSJEmSJEmSJEmSpKphAFqSJEmSJEmSJEmSJEmSJElS1TAALUmSJEmSJEmSJEmSJEmSJKlqGICWJEmSJEmSJEmSJEmSJEmSVDUMQEuSJEmSJEmSJEmSJEmSJEmqGgagJUmSJEmSJEmSJEmSJEmSJFUNA9CSJEmSJEmSJEmSJEmSJEmSqoYBaEmSJEmSJEmSJEmSJEmSJElVwwC0JEmSJEmSJEmSJEmSJEmSpKphAFqSJEmSJEmSJEmSJEmSJElS1TAALUmSJEmSJEmSJEmSJEmSJKlqGICWJEmSJEmSJEmSJEmSJEmSVDUMQEuSJEmSJEmSJEmSJEmSJEmqGgagJUmSJEmSJEmSJEmSJEmSJFUNA9CSJEmSJEmSJEmSJEmSJEmSqoYBaEmSJEmSJEmSJEmSJEmSJElVwwC0JEmSJEmSJEmSJEmSJEmSpKphAFqSJEmSJEmSJEmSJEmSJElS1TAALUmSJEmSJEmSJEmSJEmSJKlqGICWJEmSJEmSJEmSJEmSJEmSVDUMQEuSJEmSJEmSJEmSJEmSJEmqGgagJUmSJEmSJEmSJEmSJEmSJFUNA9CSJEmSJEmSJEmSJEmSJEmSqoYBaEmSJEmSJEmSJEmSJEmSJElVwwC0JEmSJEmSJEmSJEmSJEmS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" # noqa diff --git a/fund_store/config/fund_loader_config/night_shelter/ns_r2.py b/fund_store/config/fund_loader_config/night_shelter/ns_r2.py new file mode 100644 index 000000000..e0f31cacc --- /dev/null +++ b/fund_store/config/fund_loader_config/night_shelter/ns_r2.py @@ -0,0 +1,225 @@ +from datetime import datetime, timezone + +from config.fund_loader_config.common_fund_config.fund_base_tree_paths import ( + NSTF_R2_BASE_PATH, +) +from config.fund_loader_config.logo import DLUHC_LOGO_PNG +from db.models.fund import FundingType + +NIGHT_SHELTER_FUND_ID = "13b95669-ed98-4840-8652-d6b7a19964db" +NIGHT_SHELTER_ROUND_2_ID = "fc7aa604-989e-4364-98a7-d1234271435a" +APPLICATION_BASE_PATH = ".".join([str(NSTF_R2_BASE_PATH), str(1)]) +ASSESSMENT_BASE_PATH = ".".join([str(NSTF_R2_BASE_PATH), str(2)]) +NS_R2_OPENS_DATE = datetime(2023, 6, 7, 12, 0, 0, tzinfo=timezone.utc) # 2023-06-07 12:00:00 +NS_R2_DEADLINE_DATE = datetime(2023, 7, 7, 11, 59, 0, tzinfo=timezone.utc) # 2023-07-07 11:59:00 +NS_R2_ASSESSMENT_DEADLINE_DATE = datetime(2023, 8, 9, 12, 0, 0, tzinfo=timezone.utc) # 2023-08-09 12:00:00 + +NIGHT_SHELTER_PROSPECTS_LINK = ( + "https://www.gov.uk/government/publications/night-shelter-transformation-fund-round-2-prospectus" +) +NIGHT_SHELTER_APPLICATION_GUIDANCE = { + "en": ( + "Read the fund's" + " prospectus before you apply.
" + ) +} + +r2_application_sections = [ + { + "section_name": {"en": "Before you start", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.1", + }, + { + "section_name": {"en": "Name your application", "cy": ""}, + "form_name_json": {"en": "name-your-application-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.1.1", + }, + { + "section_name": {"en": "1. About your organisation", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2", + }, + { + "section_name": {"en": "1.1 Organisation information", "cy": ""}, + "form_name_json": {"en": "organisation-information-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.1", + }, + { + "section_name": {"en": "1.2 Organisation type", "cy": ""}, + "form_name_json": {"en": "organisation-type-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.2", + }, + { + "section_name": {"en": "1.3 Applicant information", "cy": ""}, + "form_name_json": {"en": "applicant-information-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.3", + }, + { + "section_name": {"en": "1.4 Joint applications", "cy": ""}, + "form_name_json": {"en": "joint-applications-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.2.4", + }, + { + "section_name": {"en": "2. Your skills and experience", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3", + "weighting": 15, + }, + { + "section_name": {"en": "2.1 Staff and volunteers", "cy": ""}, + "form_name_json": {"en": "staff-and-volunteers-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.1", + }, + { + "section_name": {"en": "2.2 Current services", "cy": ""}, + "form_name_json": {"en": "current-services-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.3.2", + }, + { + "section_name": {"en": "3. Your proposal", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4", + "weighting": 40, + }, + { + "section_name": {"en": "3.1 Objectives and activities", "cy": ""}, + "form_name_json": {"en": "objectives-and-activities-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.1", + }, + { + "section_name": {"en": "3.2 Project milestones", "cy": ""}, + "form_name_json": {"en": "project-milestones-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.2", + }, + { + "section_name": {"en": "3.3 Local need and support", "cy": ""}, + "form_name_json": {"en": "local-need-and-support-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.3", + }, + { + "section_name": {"en": "3.4 Proposed services", "cy": ""}, + "form_name_json": {"en": "proposed-services-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.4", + }, + { + "section_name": {"en": "3.5 Working in partnership", "cy": ""}, + "form_name_json": {"en": "working-in-partnership-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.5", + }, + { + "section_name": {"en": "3.6 Proposal sustainability", "cy": ""}, + "form_name_json": {"en": "proposal-sustainability-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.4.6", + }, + { + "section_name": {"en": "4. Outputs and outcomes", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.5", + "weighting": 15, + }, + { + "section_name": {"en": "4.1 Outputs and outcomes", "cy": ""}, + "form_name_json": {"en": "outputs-and-outcomes-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.5.1", + }, + { + "section_name": {"en": "5. Risk and deliverability", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.6", + "weighting": 15, + }, + { + "section_name": {"en": "5.1 Risk and deliverability", "cy": ""}, + "form_name_json": {"en": "risk-and-deliverability-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.6.1", + }, + { + "section_name": {"en": "6. Value for money", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.7", + "weighting": 15, + }, + { + "section_name": {"en": "6.1 Funding required", "cy": ""}, + "form_name_json": {"en": "funding-required-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.7.1", + }, + { + "section_name": {"en": "6.2 Building works", "cy": ""}, + "form_name_json": {"en": "building-works-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.7.2", + }, + { + "section_name": {"en": "6.3 Match funding", "cy": ""}, + "form_name_json": {"en": "match-funding-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.7.3", + }, + { + "section_name": {"en": "7. Declarations", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.8", + }, + { + "section_name": {"en": "7.1 Declarations", "cy": ""}, + "form_name_json": {"en": "declarations-ns", "cy": ""}, + "tree_path": f"{APPLICATION_BASE_PATH}.8.1", + }, +] + +fund_config = { + "id": NIGHT_SHELTER_FUND_ID, + "name_json": { + "en": "Night Shelter Transformation Fund", + "cy": "", + }, + "title_json": { + "en": "funding to transform your night shelter services in England", + "cy": "", + }, + "funding_type": FundingType.COMPETITIVE, + "short_name": "NSTF", + "description_json": {"en": "", "cy": ""}, + "welsh_available": False, + "owner_organisation_name": "Department for Levelling Up, Housing and Communities", + "owner_organisation_shortname": "DLUHC", + "owner_organisation_logo_uri": DLUHC_LOGO_PNG, +} + +round_config = [ + { + "id": NIGHT_SHELTER_ROUND_2_ID, + "fund_id": NIGHT_SHELTER_FUND_ID, + "title_json": {"en": "Round 2", "cy": ""}, + "short_name": "R2", + "opens": NS_R2_OPENS_DATE, + "assessment_start": None, + "deadline": NS_R2_DEADLINE_DATE, + "application_reminder_sent": True, + "reminder_date": None, + "assessment_deadline": NS_R2_ASSESSMENT_DEADLINE_DATE, + "prospectus": NIGHT_SHELTER_PROSPECTS_LINK, + "privacy_notice": "https://www.gov.uk/guidance/night-shelter-transformation-fund-2022-2025-privacy-notice", + "reference_contact_page_over_email": False, + "contact_us_banner_json": {"en": "", "cy": ""}, + "contact_email": "transformationfund@levellingup.gov.uk", + "contact_phone": None, + "contact_textphone": None, + "support_times": "9am to 5pm", + "support_days": "Monday to Friday", + "instructions_json": None, + "feedback_link": "https://forms.office.com/e/n6J9KPebUy", + "project_name_field_id": "YVsPtE", + "application_guidance_json": NIGHT_SHELTER_APPLICATION_GUIDANCE, + "guidance_url": ( + "https://mhclg.sharepoint.com.mcas.ms/:w:/s/HomelessnessandRoughSleeping/EZn" + "-Dq3eBvFDtdBqhyEZxUUBj_BP53F9TVyI0imX3NdcPw?e=PtmLwH" + ), + "all_uploaded_documents_section_available": False, + "application_fields_download_available": False, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": False, + "feedback_survey_config": { + "has_feedback_survey": False, + "has_section_feedback": False, + "is_feedback_survey_optional": True, + "is_section_feedback_optional": True, + }, + "eligibility_config": {"has_eligibility": False}, + "eoi_decision_schema": None, + } +] diff --git a/fund_store/copilot/.workspace b/fund_store/copilot/.workspace new file mode 100644 index 000000000..92b20589f --- /dev/null +++ b/fund_store/copilot/.workspace @@ -0,0 +1 @@ +application: pre-award diff --git a/fund_store/copilot/environments/addons/assessment-import-queue.yml b/fund_store/copilot/environments/addons/assessment-import-queue.yml new file mode 100644 index 000000000..b45fabd43 --- /dev/null +++ b/fund_store/copilot/environments/addons/assessment-import-queue.yml @@ -0,0 +1,50 @@ +Parameters: + App: + Type: String + Description: Your application's name. + Env: + Type: String + Description: The environment name your service, job, or workflow is being deployed to. + FifoQueueName: + Type: String + Description: Fifo Queue Name + Default: assessment-import-queue + +Resources: + AssessmentImportQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: !Sub ${FifoQueueName}-${Env}.fifo + FifoQueue: true + RedrivePolicy: + deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn + maxReceiveCount: 3 + DeadLetterQueue: + Type: AWS::SQS::Queue + Properties: + FifoQueue: true + QueueName: !Sub ${FifoQueueName}-${Env}-deadletter.fifo + + + +Outputs: + AssessmentImportQueueURL: + Description: Queue URL for Fifo queue + Value: !Ref AssessmentImportQueue + Export: + Name: !Sub ${App}-${Env}-AssessmentImportQueueURL + AssessmentImportQueueArn: + Description: Queue Arn for FIFO queue + Value: !GetAtt AssessmentImportQueue.Arn + Export: + Name: !Sub ${App}-${Env}-AssessmentImportQueueArn + DeadLetterQueueURL: + Description: "URL of dead-letter queue" + Value: !Ref DeadLetterQueue + Export: + Name: !Sub ${App}-${Env}-DeadLetterQueueURL + DeadLetterQueueARN: + Description: "ARN of dead-letter queue" + Value: !GetAtt DeadLetterQueue.Arn + Export: + Name: !Sub ${App}-${Env}-DeadLetterQueueARN diff --git a/fund_store/copilot/environments/addons/form-uploads.yml b/fund_store/copilot/environments/addons/form-uploads.yml new file mode 100644 index 000000000..bd25feedd --- /dev/null +++ b/fund_store/copilot/environments/addons/form-uploads.yml @@ -0,0 +1,61 @@ +Parameters: + App: + Type: String + Description: Your application's name. + Env: + Type: String + Description: The environment name your service, job, or workflow is being deployed to. + +Resources: + FormUploadsBucket: + Metadata: + 'aws:copilot:description': 'An Amazon S3 bucket, form-uploads, for storing and retrieving objects' + Type: AWS::S3::Bucket + Properties: + AccessControl: Private + BucketName: !Sub fsd-form-uploads-${Env} + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + OwnershipControls: + Rules: + - ObjectOwnership: BucketOwnerEnforced + + FormUploadsBucketPolicy: + Metadata: + 'aws:copilot:description': 'A bucket policy to deny unencrypted access to the bucket and its contents' + Type: AWS::S3::BucketPolicy + DeletionPolicy: Retain + Properties: + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: ForceHTTPS + Effect: Deny + Principal: '*' + Action: 's3:*' + Resource: + - !Sub ${ FormUploadsBucket.Arn}/* + - !Sub ${ FormUploadsBucket.Arn} + Condition: + Bool: + "aws:SecureTransport": false + Bucket: !Ref FormUploadsBucket + +Outputs: + FormUploadsName: + Description: "The name of a user-defined bucket." + Value: !Ref FormUploadsBucket + Export: + Name: !Sub ${App}-${Env}-FormUploadsBucket + FormUploadsBucketARN: + Description: "The ARN of the form-uploads bucket." + Value: !GetAtt FormUploadsBucket.Arn + Export: + Name: !Sub ${App}-${Env}-FormUploadsBucketARN diff --git a/fund_store/copilot/environments/addons/funding-service-magic-links.yml b/fund_store/copilot/environments/addons/funding-service-magic-links.yml new file mode 100644 index 000000000..01954162b --- /dev/null +++ b/fund_store/copilot/environments/addons/funding-service-magic-links.yml @@ -0,0 +1,94 @@ +Parameters: + App: + Type: String + Description: Your application's name. + Env: + Type: String + Description: The environment name your service, job, or workflow is being deployed to. + +Resources: + # Subnet group to control where the Redis gets placed + RedisSubnetGroup: + Type: AWS::ElastiCache::SubnetGroup + Properties: + Description: Group of subnets to place Redis into + SubnetIds: !Split [ ',', { 'Fn::ImportValue': !Sub '${App}-${Env}-PrivateSubnets' } ] + + # Security group to add the Redis cluster to the VPC, + # and to allow the Fargate containers to talk to Redis on port 6379 + RedisSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: "Redis Security Group" + VpcId: + Fn::ImportValue: + !Sub '${App}-${Env}-VpcId' + + # Enable ingress from other ECS services created within the environment. + RedisIngress: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Ingress from Fargate containers + GroupId: !Ref 'RedisSecurityGroup' + IpProtocol: tcp + FromPort: 6379 + ToPort: 6379 + SourceSecurityGroupId: + Fn::ImportValue: + !Sub '${App}-${Env}-EnvironmentSecurityGroup' + + # Secret Storage of access credentials + RedisSecret: + Metadata: + 'aws:copilot:description': 'A Secrets Manager secret to store your DB credentials' + Type: AWS::SecretsManager::Secret + Properties: + Description: !Sub 'Redis main user secret for ${AWS::StackName}' + GenerateSecretString: + SecretStringTemplate: '{"username": "redis"}' + GenerateStringKey: "password" + ExcludePunctuation: true + IncludeSpace: false + PasswordLength: 16 + + # Creation of the cluster itself + RedisReplicationGroup: + Type: AWS::ElastiCache::ReplicationGroup + Properties: + ReplicationGroupId: !Sub 'funding-service-magic-links-${Env}' + ReplicationGroupDescription: !Sub '${Env} Funding Service Magic Links' + AutomaticFailoverEnabled: true + AtRestEncryptionEnabled: true + TransitEncryptionEnabled: true + AutoMinorVersionUpgrade: true + MultiAZEnabled: true + CacheNodeType: cache.m5.large + CacheSubnetGroupName: !Ref 'RedisSubnetGroup' + SecurityGroupIds: + - !GetAtt 'RedisSecurityGroup.GroupId' + Engine: redis + NumCacheClusters: 2 + + # Redis endpoint stored in SSM so that other services can retrieve the endpoint. + RedisEndpointAddressParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub '/${App}/${Env}/redis' # Other services can retrieve the endpoint from this path. + Type: String + Value: !GetAtt 'RedisReplicationGroup.PrimaryEndPoint.Address' + +Outputs: + RedisEndpoint: + Description: The endpoint of the redis cluster + Value: !GetAtt 'RedisReplicationGroup.PrimaryEndPoint.Address' + Export: + Name: !Sub ${App}-${Env}-RedisEndpoint + RedisInstanceURI: + Description: "The URI of the redis cluster." + Value: + !Sub + - "rediss://${HOSTNAME}:${PORT}" + - HOSTNAME: !GetAtt 'RedisReplicationGroup.PrimaryEndPoint.Address' + PORT: !GetAtt 'RedisReplicationGroup.PrimaryEndPoint.Port' + Export: + Name: !Sub ${App}-${Env}-RedisInstanceURI diff --git a/fund_store/copilot/environments/dev/manifest.yml b/fund_store/copilot/environments/dev/manifest.yml new file mode 100644 index 000000000..e9147bc99 --- /dev/null +++ b/fund_store/copilot/environments/dev/manifest.yml @@ -0,0 +1,41 @@ +# The manifest for the "dev" environment. +# Read the full specification for the "Environment" type at: +# https://aws.github.io/copilot-cli/docs/manifest/environment/ + +# Your environment name will be used in naming your resources like VPC, cluster, etc. +name: dev +type: Environment + +# Import your own VPC and subnets or configure how they should be created. +# Run this in uat/production only - in the test environments, these should be ad-hoc per deployment +network: + vpc: + id: 'vpc-0850970940cee0412' + subnets: + public: + - id: 'subnet-0f7aa03feb2923658' + - id: 'subnet-0a8dfef78a0873187' + private: + - id: 'subnet-03caaa338a263f66f' + - id: 'subnet-0f4bdb0fe7e467743' + +# Configure the load balancers in your environment, once created. +# http: +# public: +# private: + +# Configure observability for your environment resources. +observability: + container_insights: false + +cdn: true + +http: + public: + security_groups: + ingress: + restrict_to: + cdn: true + private: + ingress: + vpc: true # Enable incoming traffic within the VPC to the internal load balancer. diff --git a/fund_store/copilot/environments/test/manifest.yml b/fund_store/copilot/environments/test/manifest.yml new file mode 100644 index 000000000..c7d611627 --- /dev/null +++ b/fund_store/copilot/environments/test/manifest.yml @@ -0,0 +1,41 @@ +# The manifest for the "test" environment. +# Read the full specification for the "Environment" type at: +# https://aws.github.io/copilot-cli/docs/manifest/environment/ + +# Your environment name will be used in naming your resources like VPC, cluster, etc. +name: test +type: Environment + +# Import your own VPC and subnets or configure how they should be created. +# Run this in uat/production only - in the test environments, these should be ad-hoc per deployment +network: + vpc: + id: 'vpc-0ca7bdd50d5dba428' + subnets: + public: + - id: 'subnet-0f1f40929bdabbcdd' + - id: 'subnet-0e686586655747458' + private: + - id: 'subnet-07f5736fe61f32266' + - id: 'subnet-054d3a0257e2c809d' + +# Configure the load balancers in your environment, once created. +# http: +# public: +# private: + +# Configure observability for your environment resources. +observability: + container_insights: false + +cdn: true + +http: + public: + security_groups: + ingress: + restrict_to: + cdn: true + private: + ingress: + vpc: true # Enable incoming traffic within the VPC to the internal load balancer. diff --git a/fund_store/copilot/fsd-fund-store/addons/fsd-fund-store-cluster.yml b/fund_store/copilot/fsd-fund-store/addons/fsd-fund-store-cluster.yml new file mode 100644 index 000000000..a3da805c2 --- /dev/null +++ b/fund_store/copilot/fsd-fund-store/addons/fsd-fund-store-cluster.yml @@ -0,0 +1,158 @@ +Parameters: + App: + Type: String + Description: Your application's name. + Env: + Type: String + Description: The environment name your service, job, or workflow is being deployed to. + Name: + Type: String + Description: The name of the service, job, or workflow being deployed. + # Customize your Aurora Serverless cluster by setting the default value of the following parameters. + fsdfundstoreclusterDBName: + Type: String + Description: The name of the initial database to be created in the Aurora Serverless v2 cluster. + Default: fsd_fund_store + # Cannot have special characters + # Naming constraints: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits.html#RDS_Limits.Constraints +Mappings: + fsdfundstoreclusterEnvScalingConfigurationMap: + All: + "DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128 + "DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128 + BastionMap: + dev: + "SecurityGroup": "sg-0b6c7aabb95bf14a9" + test: + "SecurityGroup": "sg-0cf75a004dbade7b8" + uat: + "SecurityGroup": "sg-04017abfef2079894" + prod: + "SecurityGroup": "sg-08cecea8f9b8a4ec9" + +Resources: + fsdfundstoreclusterDBSubnetGroup: + Type: 'AWS::RDS::DBSubnetGroup' + Properties: + DBSubnetGroupDescription: Group of Copilot private subnets for Aurora Serverless v2 cluster. + SubnetIds: + !Split [',', { 'Fn::ImportValue': !Sub '${App}-${Env}-PrivateSubnets' }] + fsdfundstoreclusterSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your workload to access the Aurora Serverless v2 cluster fsdfundstorecluster' + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: !Sub 'The Security Group for ${Name} to access Aurora Serverless v2 cluster fsdfundstorecluster.' + VpcId: + Fn::ImportValue: + !Sub '${App}-${Env}-VpcId' + Tags: + - Key: Name + Value: !Sub 'copilot-${App}-${Env}-${Name}-Aurora' + fsdfundstoreclusterDBClusterSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your Aurora Serverless v2 cluster fsdfundstorecluster' + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: The Security Group for the Aurora Serverless v2 cluster. + SecurityGroupIngress: + - ToPort: 5432 + FromPort: 5432 + IpProtocol: tcp + Description: !Sub 'From the Aurora Security Group of the workload ${Name}.' + SourceSecurityGroupId: !Ref fsdfundstoreclusterSecurityGroup + - ToPort: 5432 + FromPort: 5432 + IpProtocol: tcp + Description: !Sub 'From the Bastion Security Group.' + SourceSecurityGroupId: !FindInMap [BastionMap, !Ref Env, 'SecurityGroup'] + VpcId: + Fn::ImportValue: + !Sub '${App}-${Env}-VpcId' + Tags: + - Key: Name + Value: !Sub 'copilot-${App}-${Env}-${Name}-Aurora' + fsdfundstoreclusterAuroraSecret: + Metadata: + 'aws:copilot:description': 'A Secrets Manager secret to store your DB credentials' + Type: AWS::SecretsManager::Secret + Properties: + Description: !Sub Aurora main user secret for ${AWS::StackName} + GenerateSecretString: + SecretStringTemplate: '{"username": "postgres"}' + GenerateStringKey: "password" + ExcludePunctuation: true + IncludeSpace: false + PasswordLength: 16 + fsdfundstoreclusterDBClusterParameterGroup: + Metadata: + 'aws:copilot:description': 'A DB parameter group for engine configuration values' + Type: 'AWS::RDS::DBClusterParameterGroup' + Properties: + Description: !Ref 'AWS::StackName' + Family: 'aurora-postgresql14' + Parameters: + client_encoding: 'UTF8' + fsdfundstoreclusterDBCluster: + Metadata: + 'aws:copilot:description': 'The fsdfundstorecluster Aurora Serverless v2 database cluster' + Type: 'AWS::RDS::DBCluster' + Properties: + MasterUsername: + !Join [ "", [ '{{resolve:secretsmanager:', !Ref fsdfundstoreclusterAuroraSecret, ":SecretString:username}}" ]] # pragma: allowlist secret + MasterUserPassword: + !Join [ "", [ '{{resolve:secretsmanager:', !Ref fsdfundstoreclusterAuroraSecret, ":SecretString:password}}" ]] # pragma: allowlist secret + DatabaseName: !Ref fsdfundstoreclusterDBName + Engine: 'aurora-postgresql' + EngineVersion: '14.4' + DBClusterParameterGroupName: !Ref fsdfundstoreclusterDBClusterParameterGroup + DBSubnetGroupName: !Ref fsdfundstoreclusterDBSubnetGroup + Port: 5432 + StorageEncrypted: true + BackupRetentionPeriod: 8 + VpcSecurityGroupIds: + - !Ref fsdfundstoreclusterDBClusterSecurityGroup + ServerlessV2ScalingConfiguration: + # Replace "All" below with "!Ref Env" to set different autoscaling limits per environment. + MinCapacity: !FindInMap [fsdfundstoreclusterEnvScalingConfigurationMap, All, DBMinCapacity] + MaxCapacity: !FindInMap [fsdfundstoreclusterEnvScalingConfigurationMap, All, DBMaxCapacity] + fsdfundstoreclusterDBWriterInstance: + Metadata: + 'aws:copilot:description': 'The fsdfundstorecluster Aurora Serverless v2 writer instance' + Type: 'AWS::RDS::DBInstance' + Properties: + DBClusterIdentifier: !Ref fsdfundstoreclusterDBCluster + DBInstanceClass: db.serverless + Engine: 'aurora-postgresql' + PromotionTier: 1 + AvailabilityZone: !Select + - 0 + - !GetAZs + Ref: AWS::Region + + fsdfundstoreclusterSecretAuroraClusterAttachment: + Type: AWS::SecretsManager::SecretTargetAttachment + Properties: + SecretId: !Ref fsdfundstoreclusterAuroraSecret + TargetId: !Ref fsdfundstoreclusterDBCluster + TargetType: AWS::RDS::DBCluster +Outputs: + DatabaseUrl: + Description: "The URL of this database." + Value: + !Sub + - "postgres://${USERNAME}:${PASSWORD}@${HOSTNAME}:${PORT}/${DBNAME}" + - USERNAME: !Join [ "", [ '{{resolve:secretsmanager:', !Ref fsdfundstoreclusterAuroraSecret, ":SecretString:username}}" ]] # pragma: allowlist secret + PASSWORD: !Join [ "", [ '{{resolve:secretsmanager:', !Ref fsdfundstoreclusterAuroraSecret, ":SecretString:password}}" ]] # pragma: allowlist secret + HOSTNAME: !Join [ "", [ '{{resolve:secretsmanager:', !Ref fsdfundstoreclusterAuroraSecret, ":SecretString:host}}" ]] # pragma: allowlist secret + PORT: !Join [ "", [ '{{resolve:secretsmanager:', !Ref fsdfundstoreclusterAuroraSecret, ":SecretString:port}}" ]] # pragma: allowlist secret + DBNAME: !Join [ "", [ '{{resolve:secretsmanager:', !Ref fsdfundstoreclusterAuroraSecret, ":SecretString:dbname}}" ]] # pragma: allowlist secret + + fsdfundstoreclusterSecret: # injected as FSDFUNDSTORECLUSTER_SECRET environment variable by Copilot. + Description: "The JSON secret that holds the database username and password. Fields are 'host', 'port', 'dbname', 'username', 'password', 'dbClusterIdentifier' and 'engine'" + Value: !Ref fsdfundstoreclusterAuroraSecret + fsdfundstoreclusterSecurityGroup: + Description: "The security group to attach to the workload." + Value: !Ref fsdfundstoreclusterSecurityGroup + Export: + Name: fsdfundstoreclusterSecurityGroup diff --git a/fund_store/copilot/fsd-fund-store/manifest.yml b/fund_store/copilot/fsd-fund-store/manifest.yml new file mode 100644 index 000000000..e5b95a981 --- /dev/null +++ b/fund_store/copilot/fsd-fund-store/manifest.yml @@ -0,0 +1,88 @@ +# The manifest for the "data-frontend" service. +# Read the full specification for the "Load Balanced Web Service" type at: +# https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/ + +# Your service name will be used in naming your resources like log groups, ECS services, etc. +name: fsd-fund-store +type: Backend Service + +# Distribute traffic to your service. +http: + # Requests to this path will be forwarded to your service. + # To match all requests you can use the "/" path. + path: '/' + # You can specify a custom health check path. The default is "/". + healthcheck: '/healthcheck' + +# Configuration for your containers and service. +image: + # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#image-location + location: ghcr.io/communitiesuk/funding-service-design-fund-store:latest + # Port exposed through your container to route traffic to it. + port: 8080 + +# Valid values: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html +# Number of CPU units for the task. +cpu: 512 +# Amount of memory in MiB used by the task. +memory: 1024 + +# See https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#platform +platform: linux/x86_64 +# Number of tasks that should be running in your service. +count: 1 +# Enable running commands in your container. +exec: true + +network: + connect: true # Enable Service Connect for intra-environment traffic between services. + +# storage: + # readonly_fs: true # Limit to read-only access to mounted root filesystems. + +# Optional fields for more advanced use-cases. +# +# Pass environment variables as key value pairs. +variables: + SENTRY_DSN: "https://5ea1346b1c1b4dd8af0c95435ca77945@o1432034.ingest.sentry.io/4503903486148608" + FLASK_ENV: ${COPILOT_ENVIRONMENT_NAME} + PORT: 8080 + +secrets: + SECRET_KEY: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/SECRET_KEY + +# You can override any of the values defined above by environment. +environments: + dev: + count: + spot: 1 + test: + deployment: + rolling: 'recreate' + count: + spot: 2 + uat: + count: + range: 2-4 + cooldown: + in: 60s + out: 30s + cpu_percentage: + value: 70 + memory_percentage: + value: 80 + requests: 30 + response_time: 2s + prod: + count: + range: 2-4 + cooldown: + in: 60s + out: 30s + cpu_percentage: + value: 70 + memory_percentage: + value: 80 + requests: 30 + variables: + FLASK_ENV: production diff --git a/fund_store/db/__init__.py b/fund_store/db/__init__.py new file mode 100644 index 000000000..3f48a9c84 --- /dev/null +++ b/fund_store/db/__init__.py @@ -0,0 +1,17 @@ +from flask_migrate import Migrate +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import MetaData + +convention = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s", +} + +metadata = MetaData(naming_convention=convention) + +db = SQLAlchemy(metadata=metadata) + +migrate = Migrate() diff --git a/fund_store/db/models/__init__.py b/fund_store/db/models/__init__.py new file mode 100644 index 000000000..6787934d5 --- /dev/null +++ b/fund_store/db/models/__init__.py @@ -0,0 +1,21 @@ +from .event import Event # noqa +from .form_name import FormName # noqa +from .fund import Fund # noqa +from .round import Round # noqa +from .section import AssessmentField # noqa +from .section import Section # noqa +from .section import SectionField # noqa + +# from .translations import Translation # noqa + +# from .section import section_field_table # noqa + +__all__ = [ + "Round", + "Fund", + "Section", + "AssessmentField", + "SectionField", + "FormName", + "Event", +] diff --git a/fund_store/db/models/event.py b/fund_store/db/models/event.py new file mode 100644 index 000000000..b583320c7 --- /dev/null +++ b/fund_store/db/models/event.py @@ -0,0 +1,36 @@ +import uuid +from enum import Enum + +from flask_sqlalchemy.model import DefaultMeta +from sqlalchemy import Column, DateTime, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.types import Enum as SQLAEnum + +from db import db + +BaseModel: DefaultMeta = db.Model + + +class EventType(Enum): + APPLICATION_DEADLINE_REMINDER = "APPLICATION_DEADLINE_REMINDER" + SEND_INCOMPLETE_APPLICATIONS = "SEND_INCOMPLETE_APPLICATIONS" + ACCOUNT_IMPORT = "ACCOUNT_IMPORT" + + +class Event(BaseModel): + id = Column( + "id", + UUID(as_uuid=True), + default=uuid.uuid4, + primary_key=True, + nullable=False, + ) + round_id = Column( + "round_id", + UUID(as_uuid=True), + ForeignKey("round.id"), + nullable=True, + ) + type = Column("type", SQLAEnum(EventType, name="event_type"), nullable=False, unique=False) + activation_date = Column("activation_date", DateTime(), nullable=False) + processed = Column("processed", DateTime(), nullable=True) diff --git a/fund_store/db/models/form_name.py b/fund_store/db/models/form_name.py new file mode 100644 index 000000000..e3036e05f --- /dev/null +++ b/fund_store/db/models/form_name.py @@ -0,0 +1,22 @@ +from flask_sqlalchemy.model import DefaultMeta +from sqlalchemy import JSON, Column, ForeignKey, Integer + +from db import db + +BaseModel: DefaultMeta = db.Model + + +class FormName(BaseModel): + section_id = Column( + "section_id", + Integer, + ForeignKey("section.id"), + nullable=False, + primary_key=True, + ) + form_name_json = Column( + "form_name_json", + JSON(none_as_null=True), + nullable=False, + unique=False, + ) diff --git a/fund_store/db/models/fund.py b/fund_store/db/models/fund.py new file mode 100644 index 000000000..09aece5a8 --- /dev/null +++ b/fund_store/db/models/fund.py @@ -0,0 +1,42 @@ +import uuid +from enum import Enum +from typing import List + +from flask_sqlalchemy.model import DefaultMeta +from sqlalchemy import JSON, Column +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, relationship +from sqlalchemy.types import Boolean +from sqlalchemy.types import Enum as SQLAEnum + +from db import db +from db.models.round import Round + +BaseModel: DefaultMeta = db.Model + + +class FundingType(Enum): + COMPETITIVE = "COMPETITIVE" + UNCOMPETED = "UNCOMPETED" + EOI = "EOI" + + +class Fund(BaseModel): + id = Column( + "id", + UUID(as_uuid=True), + default=uuid.uuid4, + primary_key=True, + nullable=False, + ) + name_json = Column("name_json", JSON(none_as_null=True), nullable=False, unique=False) + title_json = Column("title_json", JSON(none_as_null=True), nullable=False, unique=False) + short_name = Column("short_name", db.String(), nullable=False, unique=True) + description_json = Column("description_json", JSON(none_as_null=True), nullable=False, unique=False) + rounds: Mapped[List["Round"]] = relationship("Round") + welsh_available = Column("welsh_available", Boolean, default=False, nullable=False) + owner_organisation_name = Column("owner_organisation_name", db.String(), nullable=False, unique=False) + owner_organisation_shortname = Column("owner_organisation_shortname", db.String(), nullable=False, unique=False) + owner_organisation_logo_uri = Column("owner_organisation_logo_uri", db.Text(), nullable=True, unique=False) + funding_type = Column("funding_type", SQLAEnum(FundingType, name="fundingtype"), nullable=False, unique=False) + ggis_scheme_reference_number = Column("ggis_scheme_reference_number", db.String(255), nullable=True, unique=False) diff --git a/fund_store/db/models/round.py b/fund_store/db/models/round.py new file mode 100644 index 000000000..c8bcf7009 --- /dev/null +++ b/fund_store/db/models/round.py @@ -0,0 +1,95 @@ +import uuid + +from flask_sqlalchemy.model import DefaultMeta +from sqlalchemy import JSON, Column, DateTime, ForeignKey, UniqueConstraint +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.types import Boolean + +from db import db + +BaseModel: DefaultMeta = db.Model + + +class Round(BaseModel): + __table_args__ = (UniqueConstraint("fund_id", "short_name"),) + id = Column( + "id", + UUID(as_uuid=True), + default=uuid.uuid4, + primary_key=True, + nullable=False, + ) + # fund_id: Mapped[UUID] = mapped_column(ForeignKey("fund.id")) + fund_id = Column( + "fund_id", + UUID(as_uuid=True), + ForeignKey("fund.id"), + nullable=False, + ) + title_json = Column("title_json", JSON(none_as_null=True), nullable=False, unique=False) + short_name = Column("short_name", db.String(), nullable=False, unique=False) + opens = Column("opens", DateTime()) + deadline = Column("deadline", DateTime()) + assessment_start = Column("assessment_start", DateTime()) + application_reminder_sent = Column( + "application_reminder_sent", + db.Boolean, + default=False, + nullable=False, + ) + reminder_date = Column("reminder_date", DateTime()) + assessment_deadline = Column("assessment_deadline", DateTime()) + prospectus = Column("prospectus", db.String(), nullable=False, unique=False) + privacy_notice = Column("privacy_notice", db.String(), nullable=False, unique=False) + contact_us_banner_json = Column("contact_us_banner_json", JSON(none_as_null=True), nullable=True, unique=False) + reference_contact_page_over_email = Column( + "reference_contact_page_over_email", + db.Boolean, + default=False, + nullable=False, + ) + contact_email = Column("contact_email", db.String(), nullable=True, unique=False) + contact_phone = Column("contact_phone", db.String(), nullable=True, unique=False) + contact_textphone = Column("contact_textphone", db.String(), nullable=True, unique=False) + support_times = Column("support_times", db.String(), nullable=False, unique=False) + support_days = Column("support_days", db.String(), nullable=False, unique=False) + instructions_json = Column("instructions_json", JSON(none_as_null=True), nullable=True, unique=False) + feedback_link = Column("feedback_link", db.String(), unique=False) + project_name_field_id = Column("project_name_field_id", db.String(), unique=False, nullable=False) + application_guidance_json = Column( + "application_guidance_json", JSON(none_as_null=True), nullable=True, unique=False + ) + guidance_url = Column("guidance_url", db.String(), nullable=True, unique=False) + all_uploaded_documents_section_available = Column( + "all_uploaded_documents_section_available", + Boolean, + default=False, + nullable=False, + ) + application_fields_download_available = Column( + "application_fields_download_available", + db.Boolean, + default=False, + nullable=False, + ) + display_logo_on_pdf_exports = Column( + "display_logo_on_pdf_exports", + db.Boolean, + default=False, + nullable=False, + ) + mark_as_complete_enabled = Column( + "mark_as_complete_enabled", + db.Boolean, + default=False, + nullable=False, + ) + is_expression_of_interest = Column( + "is_expression_of_interest", + db.Boolean, + default=False, + nullable=False, + ) + feedback_survey_config = Column("feedback_survey_config", JSON(none_as_null=True), nullable=True, unique=False) + eligibility_config = Column("eligibility_config", JSON(none_as_null=True), nullable=True, unique=False) + eoi_decision_schema = Column("eoi_decision_schema ", JSON(none_as_null=True), nullable=True, unique=False) diff --git a/fund_store/db/models/section.py b/fund_store/db/models/section.py new file mode 100644 index 000000000..324ceb2c5 --- /dev/null +++ b/fund_store/db/models/section.py @@ -0,0 +1,103 @@ +from flask_sqlalchemy.model import DefaultMeta +from sqlalchemy import JSON, Column, ForeignKey, Index, Integer, func +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import foreign, relationship, remote +from sqlalchemy_utils import LtreeType + +from db import db + +BaseModel: DefaultMeta = db.Model + + +# section_field_table = Table( +# "section_field", +# metadata, +# Column("section_id", ForeignKey("section.id"), primary_key=True), +# Column("field_id", ForeignKey("assessment_field.id"), primary_key=True), +# Column("display_order", Integer, nullable=False, unique=False), +# ) + + +class SectionField(BaseModel): + __tablename__ = "section_field" + section_id = Column(ForeignKey("section.id"), primary_key=True) + field_id = Column(ForeignKey("assessment_field.id"), primary_key=True) + display_order = Column("display_order", Integer, nullable=False, unique=False) + field = relationship("AssessmentField") + + +class AssessmentField(BaseModel): + id = Column(db.String, primary_key=True, nullable=False, unique=True) + field_type = Column( + "field_type", + db.String(), + nullable=False, + unique=False, + ) + display_type = Column("display_type", db.String(), nullable=False, unique=False) + title = Column("title", db.String(), nullable=False, unique=False) + # sections = relationship( + # "Section", secondary=section_field_table, back_populates="fields" + # ) + + +class Section(BaseModel): + id = Column( + Integer, + autoincrement=True, + primary_key=True, + nullable=False, + ) + title_json = Column( + "title_json", + JSON(none_as_null=True), + nullable=False, + unique=False, + ) + requires_feedback = Column( + "requires_feedback", + db.Boolean, + default=False, + nullable=False, + ) + + # title_content_id = mapped_column(Integer, nullable=True) + # title_translations = relationship("Translation", primaryjoin= + # "Section.title_content_id == Translation.content_id", viewonly=True) + # title_translations = relationship("Translation", + # primaryjoin="and_(foreign( + # Section.title_content_id) == Translation.content_id, + # Translation.language.like('%'))", + # viewonly=True) + round_id = Column( + UUID(as_uuid=True), + ForeignKey("round.id"), + nullable=False, + ) + weighting = Column( + Integer, + nullable=True, + ) + path = Column(LtreeType, nullable=False) + __table_args__ = (Index("ix_sections_path", path, postgresql_using="gist"),) + fields = relationship("SectionField", order_by=SectionField.display_order, viewonly=True) + # fields = relationship( + # "AssessmentField", + # secondary=section_field_table, + # back_populates="sections", + # ) + parent = relationship( + "Section", + primaryjoin=remote(path) == foreign(func.subpath(path, 0, -1)), + backref="children", + viewonly=True, + order_by="Section.path", + ) + + form_name = relationship("FormName") + + def __str__(self): + return self.title_json.get("en") + + def __repr__(self): + return "Section({})".format(self.title_json) diff --git a/fund_store/db/models/translations.py b/fund_store/db/models/translations.py new file mode 100644 index 000000000..a8f20f98a --- /dev/null +++ b/fund_store/db/models/translations.py @@ -0,0 +1,17 @@ +from flask_sqlalchemy.model import DefaultMeta +from sqlalchemy import Column, Integer + +from db import db + +BaseModel: DefaultMeta = db.Model + + +class Translation(BaseModel): + content_id = Column( + "content_id", + Integer, + primary_key=True, + nullable=False, + ) + language = Column("language", db.String(), nullable=False, unique=False, primary_key=True) + text = Column("text", db.String(), nullable=False, unique=False) diff --git a/fund_store/db/queries.py b/fund_store/db/queries.py new file mode 100644 index 000000000..694dbc552 --- /dev/null +++ b/fund_store/db/queries.py @@ -0,0 +1,593 @@ +import uuid +from datetime import datetime +from typing import List + +from sqlalchemy import bindparam, exc, func, insert, select, text, update +from sqlalchemy.dialects.postgresql import insert as postgres_insert +from sqlalchemy.sql import expression +from sqlalchemy_utils import Ltree +from sqlalchemy_utils.types.ltree import LQUERY + +from db import db +from db.models.event import Event +from db.models.form_name import FormName +from db.models.fund import Fund +from db.models.round import Round +from db.models.section import AssessmentField, Section, SectionField + + +def get_all_funds() -> List[Fund]: + funds = db.session.scalars(select(Fund)).all() + return funds + + +def get_fund_by_id( + fund_id: str, +) -> Fund: + fund = db.session.scalars(select(Fund).filter(Fund.id == fund_id)).one() + return fund + + +def get_fund_by_short_name( + fund_short_name: str, +) -> Fund: + fund = db.session.scalar(select(Fund).filter(func.lower(Fund.short_name) == func.lower(fund_short_name))) + return fund + + +def get_round_by_id( + fund_id: str, + round_id: str, +) -> Round: + round = db.session.scalars(select(Round).filter(Round.id == round_id).filter(Round.fund_id == fund_id)).one() + return round + + +def get_rounds_for_fund_by_id( + fund_id: str, +) -> List[Round]: + rounds = db.session.scalars(select(Round).filter(Round.fund_id == fund_id)).all() + return rounds + + +def get_round_by_short_name( + fund_short_name: str, + round_short_name: str, +) -> Round: + round = db.session.scalar( + select(Round) + .filter(func.lower(Round.short_name) == func.lower(round_short_name)) + .join(Fund) + .filter(func.lower(Fund.short_name) == func.lower(fund_short_name)) + ) + return round + + +def get_rounds_for_fund_by_short_name( + fund_short_name, +): + rounds = db.session.scalars( + select(Round).join(Fund).filter(func.lower(Fund.short_name) == func.lower(fund_short_name)) + ).all() + return rounds + + +def get_sections_for_round(round_id) -> List[Section]: + return db.session.scalars(select(Section).filter(Section.round_id == round_id).order_by(Section.path)).all() + + +def get_application_sections_for_round( + fund_id, + round_id, +) -> List[Section]: + application_level = db.session.scalar( + select(Section) + .filter(Section.round_id == round_id) + .filter(text("section.title_json->>'en' = 'Application'")) + .join(Round) + .filter(Round.fund_id == fund_id) + ) + if not application_level: + return None + + query = f"{application_level.path}.*{{1}}" + lquery = expression.cast(query, LQUERY) + application_sections = db.session.scalars( + select(Section).filter(Section.path.lquery(lquery)).order_by(Section.path) + ).all() + + return application_sections + + +def get_assessment_sections_for_round( + fund_id, + round_id, + language, +) -> List[Section]: + assessment_level = db.session.scalar( + select(Section) + .filter(Section.round_id == round_id) + .filter(text("section.title_json->>'en' = 'Assessment'")) + .join(Round) + .filter(Round.fund_id == fund_id) + ) + if not assessment_level: + return None + + query = f"{assessment_level.path}.*{'{1}'}" + lquery = expression.cast(query, LQUERY) + # select(Section).join(Translation, onclause=(Section.title_content_id == + # Translation.content_id) and (Translation.language == 'en'), + # isouter=True) + # .filter(Section.id == 12) + assessment_sections = db.session.scalars( + select(Section) + # .join + # ( + # Translation, + # onclause=and_((Translation.language == language), + # (Section.title_content_id == Translation.content_id)), + # isouter=True + # ) + # select (Section).filter(Section.path.lquery(lquery)) + # select(Section).join(Translation, onclause=f"section.title_content_id + # = translation.content_id and translation.language='{language}'", + # isouter=True).filter(Section.path.lquery(lquery)) + .filter(Section.path.lquery(lquery)) + .order_by(Section.path) + ).all() + + return assessment_sections + + +def create_event( + type: str, + activation_date: datetime, + round_id: str = None, + processed: datetime = None, +) -> Event: + event = Event(type=type, activation_date=activation_date, round_id=round_id, processed=processed) + try: + db.session.add(event) + db.session.commit() + db.session.refresh(event) + except exc.IntegrityError: + db.session.rollback() + return None + + return event + + +def get_events( + round_id: str = None, + type: str = None, + only_unprocessed: bool = False, +) -> List[Event]: + query = select(Event) + if round_id: + query = query.filter(Event.round_id == round_id) + + if type: + query = query.filter(Event.type == type) + + if only_unprocessed: + query = query.filter(Event.processed != None) # noqa + + events = db.session.scalars(query).all() + return events + + +def get_event(round_id: str, event_id: str) -> Event: + query = select(Event).filter(Event.id == event_id, Event.round_id == round_id) + event = db.session.scalar(query) + return event + + +def set_event_to_processed(event_id: str, processed: bool) -> Event: + event = Event.query.filter_by(id=event_id).first() + if not event: + return None + event.processed = datetime.now() if processed else None + db.session.commit() + return event + + +def upsert_fields(fields: list): + stmt = ( + ( + postgres_insert(AssessmentField).values( + id=bindparam("id"), + title=bindparam("title"), + field_type=bindparam("field_type"), + display_type=bindparam("display_type"), + ) + ) + .on_conflict_do_nothing(index_elements=[AssessmentField.id]) + .returning(AssessmentField.id) + ) + + update_params = [ + { + "id": item["form_json_id"], + "title": item["title"], + "field_type": item["type"], + "display_type": item["presentation_type"], + } + for item in fields + ] + + result = db.session.execute(stmt, update_params) + inserted_field_ids = [row.id for row in result] + return inserted_field_ids + + +def insert_sections(sections): + for section in sections: + db.session.add(section) + db.session.commit() + + +def insert_fund_data(fund_config, commit: bool = True): + stmt = ( + ( + postgres_insert(Fund).values( + id=bindparam("id"), + name_json=bindparam("name_json"), + title_json=bindparam("title_json"), + short_name=bindparam("short_name"), + description_json=bindparam("description_json"), + welsh_available=bindparam("welsh_available"), + owner_organisation_name=bindparam("owner_organisation_name"), + owner_organisation_shortname=bindparam("owner_organisation_shortname"), + owner_organisation_logo_uri=bindparam("owner_organisation_logo_uri"), + funding_type=bindparam("funding_type"), + ) + ) + .on_conflict_do_update( + index_elements=[Fund.id], + set_={ + "name_json": bindparam("name_json"), + "title_json": bindparam("title_json"), + "short_name": bindparam("short_name"), + "description_json": bindparam("description_json"), + "welsh_available": bindparam("welsh_available"), + "owner_organisation_name": bindparam("owner_organisation_name"), + "owner_organisation_shortname": bindparam("owner_organisation_shortname"), + "owner_organisation_logo_uri": bindparam("owner_organisation_logo_uri"), + "funding_type": bindparam("funding_type"), + }, + ) + .returning(Fund.id) + ) + + update_params = { + "id": fund_config["id"], + "name_json": fund_config["name_json"], + "title_json": fund_config["title_json"], + "short_name": fund_config["short_name"], + "description_json": fund_config["description_json"], + "welsh_available": fund_config["welsh_available"], + "owner_organisation_name": fund_config["owner_organisation_name"], + "owner_organisation_shortname": fund_config["owner_organisation_shortname"], + "owner_organisation_logo_uri": fund_config["owner_organisation_logo_uri"], + "funding_type": fund_config["funding_type"], + } + + result = db.session.execute(stmt, update_params) + inserted_fund_ids = [row.id for row in result] + + print(f"Prepared fund for insert: '{inserted_fund_ids}'.") + if commit: + db.session.commit() + print("DB changes committed") + return inserted_fund_ids + + +def upsert_round_data(round_configs, commit: bool = True): + # Create dictionary to store updated records + updated_rounds = {} + + for round_config in round_configs: + # Check if record exist + round_record = Round.query.filter_by(id=round_config["id"]).first() + + if round_record is not None: + # Update existing round record + round_record.title_json = round_config["title_json"] + round_record.short_name = round_config["short_name"] + round_record.opens = round_config["opens"] + round_record.assessment_start = round_config["assessment_start"] + round_record.deadline = round_config["deadline"] + round_record.application_reminder_sent = round_config["application_reminder_sent"] + round_record.reminder_date = round_config["reminder_date"] + round_record.fund_id = round_config["fund_id"] + round_record.assessment_deadline = round_config["assessment_deadline"] + round_record.prospectus = round_config["prospectus"] + round_record.privacy_notice = round_config["privacy_notice"] + round_record.reference_contact_page_over_email = round_config["reference_contact_page_over_email"] + round_record.contact_us_banner_json = round_config["contact_us_banner_json"] + round_record.contact_email = round_config["contact_email"] + round_record.contact_phone = round_config["contact_phone"] + round_record.contact_textphone = round_config["contact_textphone"] + round_record.support_times = round_config["support_times"] + round_record.support_days = round_config["support_days"] + round_record.instructions_json = round_config["instructions_json"] + round_record.project_name_field_id = round_config["project_name_field_id"] + round_record.feedback_link = round_config["feedback_link"] + round_record.application_guidance_json = round_config["application_guidance_json"] + round_record.guidance_url = round_config["guidance_url"] + round_record.all_uploaded_documents_section_available = round_config[ + "all_uploaded_documents_section_available" + ] + round_record.application_fields_download_available = round_config["application_fields_download_available"] + round_record.display_logo_on_pdf_exports = round_config["display_logo_on_pdf_exports"] + round_record.feedback_survey_config = round_config["feedback_survey_config"] + round_record.mark_as_complete_enabled = round_config["mark_as_complete_enabled"] + round_record.is_expression_of_interest = round_config["is_expression_of_interest"] + round_record.eligibility_config = round_config["eligibility_config"] + round_record.eoi_decision_schema = round_config["eoi_decision_schema"] + + updated_rounds[round_config["id"]] = round_record + + else: + # Insert new round record + new_round = Round( + id=round_config["id"], + title_json=round_config["title_json"], + short_name=round_config["short_name"], + opens=round_config["opens"], + assessment_start=round_config["assessment_start"], + deadline=round_config["deadline"], + application_reminder_sent=round_config["application_reminder_sent"], + reminder_date=round_config["reminder_date"], + fund_id=round_config["fund_id"], + assessment_deadline=round_config["assessment_deadline"], + prospectus=round_config["prospectus"], + privacy_notice=round_config["privacy_notice"], + reference_contact_page_over_email=round_config["reference_contact_page_over_email"], + contact_us_banner_json=round_config["contact_us_banner_json"], + contact_email=round_config["contact_email"], + contact_phone=round_config["contact_phone"], + contact_textphone=round_config["contact_textphone"], + support_times=round_config["support_times"], + support_days=round_config["support_days"], + instructions_json=round_config["instructions_json"], + project_name_field_id=round_config["project_name_field_id"], + feedback_link=round_config["feedback_link"], + application_guidance_json=round_config["application_guidance_json"], + guidance_url=round_config["guidance_url"], + all_uploaded_documents_section_available=round_config["all_uploaded_documents_section_available"], + application_fields_download_available=round_config["application_fields_download_available"], + display_logo_on_pdf_exports=round_config["display_logo_on_pdf_exports"], + feedback_survey_config=round_config["feedback_survey_config"], + mark_as_complete_enabled=round_config["mark_as_complete_enabled"], + is_expression_of_interest=round_config["is_expression_of_interest"], + eligibility_config=round_config["eligibility_config"], + eoi_decision_schema=round_config["eoi_decision_schema"], + ) + db.session.add(new_round) + + updated_rounds[round_config["id"]] = new_round + + print(f"Prepared rounds for insert: '{updated_rounds}'.") + if commit: + db.session.commit() + print("DB changes committed") + return updated_rounds + + +def insert_base_sections(APPLICATION_BASE_PATH, ASSESSMENT_BASE_PATH, round_id): + """ + Insert base sections for a fund round. + + :param APPLICATION_BASE_PATH: The base path for the application sections. + :param ASSESSMENT_BASE_PATH: The base path for the assessment sections. + :param round_id: The id of the round to insert the sections for. + :return: A dictionary of the inserted sections. + """ + tree_base_sections = [ + { + "section_name": {"en": "Application", "cy": "Application"}, + "tree_path": APPLICATION_BASE_PATH, + "weighting": None, + }, + { + "section_name": {"en": "Assessment", "cy": "Assessment"}, + "tree_path": ASSESSMENT_BASE_PATH, + "weighting": None, + }, + ] + + updated_sections = {} + for section in tree_base_sections: + section_record = Section.query.filter( + Section.path == Ltree(section["tree_path"]), + Section.round_id == uuid.UUID(round_id), + ).first() + + if section_record is not None: + # Update existing section record + section_record.round_id = round_id + section_record.title_json = section["section_name"] + section_record.weighting = section.get("weighting", None) + section_record.requires_feedback = section.get("requires_feedback") or False + + updated_sections[section["tree_path"]] = section_record + else: + # Insert new section record + new_section = Section( + round_id=round_id, + title_json=section["section_name"], + weighting=section.get("weighting", None), + path=Ltree(section["tree_path"]), + requires_feedback=section.get("requires_feedback") or False, + ) + db.session.add(new_section) + + updated_sections[section["tree_path"]] = new_section + + print(f"Prepared sections for insert: '{updated_sections}'.") + return updated_sections + + +def insert_or_update_application_sections(round_id, sorted_application_sections: dict): + print(f"Preparing insert for sections config: '{sorted_application_sections}'.") + updated_sections = {} + for section in sorted_application_sections: + section_record = Section.query.filter( + Section.path == Ltree(section["tree_path"]), + Section.round_id == uuid.UUID(round_id), + ).first() + + if section_record is not None: + # Update existing section record + section_id = section_record.id + section_record.round_id = round_id + section_record.title_json = section["section_name"] + section_record.weighting = (section.get("weighting", None),) + section_record.requires_feedback = section.get("requires_feedback") or False + + updated_sections[section_record.id] = section_record + print(f"Prepared section UPDATE '{section_record}'.") + else: + # Insert new section record + new_section = Section( + round_id=round_id, + title_json=section["section_name"], + weighting=section.get("weighting", None), + path=Ltree(section["tree_path"]), + requires_feedback=section.get("requires_feedback") or False, + ) + db.session.add(new_section) + db.session.commit() + section_id = new_section.id + + updated_sections[new_section.id] = new_section + print(f"Prepared section INSERT '{new_section}'.") + + if section.get("form_name_json"): + form_record = FormName.query.filter_by(section_id=section_id).first() + if form_record is not None: + form_record.form_name_json = section["form_name_json"] + print(f"Updated form name information to {section['form_name_json']}.") + else: + new_form_record = FormName(form_name_json=section["form_name_json"], section_id=section_id) + db.session.add(new_form_record) + print(f"Inserted form name information: '{new_form_record}'.") + db.session.commit() + print("Section UPDATES and INSERTS Prepared for insert.") + + return updated_sections + + +def update_application_section_names(round_id, sorted_application_sections: List[dict], language_code=None): + # TODO : Update this function to work with json objects in sorted_application_sections + for section in sorted_application_sections: + section_path = section["tree_path"] + if language_code is None: + split_section_name_list = section["section_name"].lower().split() + else: + split_section_name_list = section["section_name"][language_code].lower().split() + try: + float(split_section_name_list[0]) + split_section_name_list[1] = split_section_name_list[1].capitalize() + except ValueError: + split_section_name_list[0] = split_section_name_list[0].capitalize() + new_section_name = " ".join(split_section_name_list) + + # Update the section name + stmt = "" + if language_code is None: + stmt = ( + update(Section) + .where(Section.round_id == round_id) + .where(Section.path == Ltree(section_path)) + .values(title_json=new_section_name) + ) + else: + section["section_name"][language_code] = new_section_name + stmt = ( + update(Section) + .where(Section.round_id == round_id) + .where(Section.path == Ltree(section_path)) + .values(title_json=section["section_name"]) + ) + db.session.execute(stmt) + + db.session.commit() + + +def __add__section_fields(field_section_links): + stmt = ( + ( + postgres_insert(SectionField).values( + field_id=bindparam("field_id"), + section_id=bindparam("section_id"), + display_order=bindparam("display_order"), + ) + ) + .on_conflict_do_nothing(constraint="pk_section_field") + .returning( + SectionField.field_id, + SectionField.section_id, + SectionField.display_order, + ) + ) + + field_section_params = [ + { + "field_id": section_link["field_id"], + "section_id": section_link["section_id"], + "display_order": section_link["display_order"], + } + for section_link in field_section_links + ] + + inserted_field_section_result = db.session.execute(stmt, field_section_params).fetchall() + return inserted_field_section_result + + +def insert_assessment_sections(round_id, assessment_config: list): + sorted_assessment_sections = ( + assessment_config["sorted_scored_sections"] + assessment_config["sorted_unscored_sections"] + ) + + stmt = ( + insert(Section).values( + round_id=bindparam("round_id"), + title=bindparam("title"), + weighting=bindparam("weighting"), + path=bindparam("path"), + ) + ).returning(Section.id) + + inserted_section_ids = [] + field_section_links = [] + for section in sorted_assessment_sections: + section_params = { + "round_id": round_id, + "title": section["section_name"], + "weighting": None, + "path": Ltree(section["tree_path"]), + } + inserted_assessment_section_result = db.session.execute(stmt, section_params).fetchall() + inserted_section_id = inserted_assessment_section_result[0][0] + inserted_section_ids.append(inserted_section_id) + if "fields" in section: + for field in section["fields"]: + field_section_links.append( + { + "field_id": field["form_json_id"], + "section_id": inserted_section_id, + "display_order": field["display_order"], + } + ) + + # flush so we can see the rows in the db before committing + # db.session.commit() + inserted_section_field_links = __add__section_fields(field_section_links) + db.session.commit() + return { + "inserted_sections": inserted_section_ids, + "inserted_section_field_links": inserted_section_field_links, + } diff --git a/fund_store/db/schemas/event.py b/fund_store/db/schemas/event.py new file mode 100644 index 000000000..3bd25a2eb --- /dev/null +++ b/fund_store/db/schemas/event.py @@ -0,0 +1,12 @@ +from marshmallow import fields +from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field + +from db.models.event import Event, EventType + + +class EventSchema(SQLAlchemyAutoSchema): + class Meta: + model = Event + + round_id = auto_field() + type = fields.Enum(EventType) diff --git a/fund_store/db/schemas/fund.py b/fund_store/db/schemas/fund.py new file mode 100644 index 000000000..75f75a87a --- /dev/null +++ b/fund_store/db/schemas/fund.py @@ -0,0 +1,11 @@ +from marshmallow import fields +from marshmallow_sqlalchemy import SQLAlchemyAutoSchema + +from db.models.fund import Fund, FundingType + + +class FundSchema(SQLAlchemyAutoSchema): + class Meta: + model = Fund + + funding_type = fields.Enum(FundingType) diff --git a/fund_store/db/schemas/round.py b/fund_store/db/schemas/round.py new file mode 100644 index 000000000..0e4181c36 --- /dev/null +++ b/fund_store/db/schemas/round.py @@ -0,0 +1,11 @@ +from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field + +from db.models.round import Round + + +class RoundSchema(SQLAlchemyAutoSchema): + class Meta: + model = Round + exclude = ["eoi_decision_schema"] + + fund_id = auto_field() diff --git a/fund_store/db/schemas/section.py b/fund_store/db/schemas/section.py new file mode 100644 index 000000000..be5c8959e --- /dev/null +++ b/fund_store/db/schemas/section.py @@ -0,0 +1,97 @@ +import contextlib +from operator import itemgetter + +from marshmallow import post_dump +from marshmallow.fields import Method, String +from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field +from marshmallow_sqlalchemy.fields import Nested + +from db.models.section import Section, SectionField + + +class SectionFieldSchema(SQLAlchemyAutoSchema): + class Meta: + model = SectionField + + field_id = auto_field() + display_order = auto_field() + + def get_display_type(self, obj): + return obj.field.display_type + + def get_field_type(self, obj): + return obj.field.field_type + + display_type = Method("get_display_type") + field_type = Method("get_field_type") + + +class SectionSchema(SQLAlchemyAutoSchema): + class Meta: + model = Section + + def get_form_name(self, obj): + raise NotImplementedError + + def get_title( + self, + obj, + ): + raise NotImplementedError + + def get_weighting(self, obj): + return obj.weighting if obj.weighting else None + + path = String() + fields = Nested("SectionFieldSchema", many=True, allow_none=True) + weighting = Method("get_weighting") + + +class LocalizedSectionSchema(SectionSchema): + def get_form_name(self, obj, lang_code): + with contextlib.suppress(ValueError): + (form_name_container,) = obj.form_name + return form_name_container.form_name_json[lang_code] + + def get_title(self, obj, lang_code): + return obj.title_json.get(lang_code) + + def sort_children(self, data): + if data.get("children"): + sorted_children = sorted(data["children"], key=itemgetter("path")) + data["children"] = sorted_children + return data + + @post_dump + def sort_children_post_dump(self, data, **kwargs): + return self.sort_children(data) + + +class EnglishSectionSchema(LocalizedSectionSchema): + def get_form_name(self, obj): + return super().get_form_name(obj, "en") + + def get_title(self, obj): + return super().get_title(obj, "en") + + children = Nested("EnglishSectionSchema", many=True, allow_none=True) + form_name = Method("get_form_name") + title = Method("get_title") + + +class WelshSectionSchema(LocalizedSectionSchema): + def get_form_name(self, obj): + return super().get_form_name(obj, "cy") + + def get_title(self, obj): + return super().get_title(obj, "cy") + + children = Nested("WelshSectionSchema", many=True, allow_none=True) + form_name = Method("get_form_name") + title = Method("get_title") + + +SECTION_SCHEMA_MAP = { + "en": EnglishSectionSchema, + "cy": WelshSectionSchema, +} diff --git a/fund_store/docker-compose.yml b/fund_store/docker-compose.yml new file mode 100644 index 000000000..2d6b6aac0 --- /dev/null +++ b/fund_store/docker-compose.yml @@ -0,0 +1,19 @@ +services: + + fund-store: + build: + context: . + volumes: + - .:/fund-store:cached + command: sleep infinity + environment: + - DATABASE_URL=postgresql://postgres:password@fund-store-db:5432/fund_store + - DATABASE_URL_UNIT_TEST=postgresql://postgres:password@fund-store-db:5432/fund_store_unit_test + - FLASK_ENV=development + + + + fund-store-db: + image: postgres + environment: + - POSTGRES_PASSWORD=password diff --git a/fund_store/openapi/api.yml b/fund_store/openapi/api.yml new file mode 100644 index 000000000..acd0c8e66 --- /dev/null +++ b/fund_store/openapi/api.yml @@ -0,0 +1,442 @@ +openapi: "3.0.0" + +info: + title: Funding Service Design - Fund store. + description: Fund store API for DLUHC Funding Service Design + version: "0.2.0" + +paths: + /funds: + get: + tags: + - Funds + summary: Returns list of all funds + description: Returns list of all funds + operationId: api.routes.get_funds + responses: + 200: + description: "List all funds." + content: + application/json: + schema: + type: array + items: + $ref : 'components.yml#/components/schemas/Fund' + 404: + description: "No funds exist" + content: + texts/plain: + schema: + $ref: 'components.yml#/components/schemas/Error' + /funds/{fund_id}: + get: + operationId: api.routes.get_fund + tags: + - Funds + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - name: language + in: query + schema: + type: string + required: false + - name: use_short_name + in: query + schema: + type: boolean + required: false + responses: + 200: + description: "If the fund exists then the data is returned." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Fund' + 404: + description: "Fund not found" + content: + texts/plain: + schema: + $ref: 'components.yml#/components/schemas/Error' + /funds/{fund_id}/rounds/{round_id}: + get: + tags: + - Rounds + summary: Returns the data on a specified round for a specific fund. + description: Given a fund ID and a round ID we return the relavant round data. + operationId: api.routes.get_round + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + - name: language + in: query + schema: + type: string + - name: use_short_name + in: query + schema: + type: boolean + required: false + responses: + 200: + description: "If the round exists then the data is returned." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Round' + 404: + description: "Round not found from given fund id and round id." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Error' + /funds/{fund_id}/rounds/{round_id}/eoi_decision_schema: + get: + tags: + - Rounds + summary: Returns the EOI decision schema for the specified round + description: Given a fund ID and a round ID we return the relavant round data. + operationId: api.routes.get_eoi_deicision_schema_for_round + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + - name: language + in: query + schema: + type: string + - name: use_short_name + in: query + schema: + type: boolean + required: false + responses: + 200: + description: "If the round exists then the data is returned." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/EoiDecisionSchema' + 404: + description: "Round not found from given fund id and round id." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Error' + /funds/{fund_id}/rounds/{round_id}/events: + get: + tags: + - Rounds + summary: Returns the events set for the specified round + description: Given a fund ID and a round ID we return the associated events. + operationId: api.routes.get_events_for_round + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + - name: only_unprocessed + in: query + schema: + type: boolean + required: false + responses: + 200: + description: "If the round exists then the data is returned." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Events' + 404: + description: "Events not found from given fund id and round id." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Error' + /funds/{fund_id}/rounds/{round_id}/event/{event_id}: + get: + tags: + - Rounds + summary: Returns the event with the given fund, round and event IDs. + description: Returns the event with the given fund, round and event IDs. + operationId: api.routes.get_event_for_round + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + - name: event_id + in: path + schema: + type: string + required: true + responses: + 200: + description: "If the event exists then the data is returned." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Event' + 404: + description: "Event not found from given fund, round or event id." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Error' + put: + tags: + - Rounds + summary: Updates the event + description: Updates the event to be marked as processed + operationId: api.routes.set_round_event_to_processed + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + - name: event_id + in: path + schema: + type: string + required: true + - name: processed + in: query + schema: + type: boolean + required: true + responses: + 200: + description: Updates the event to be marked as processed + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Event' + 400: + description: "Invalid fund, round or event ID supplied" + content: + texts/plain: + schema: + $ref: 'components.yml#/components/schemas/Error' + /funds/{round_id}/application_reminder_status: + put: + tags: + - Rounds + summary: Updates the application reminder sent status to True + description: Updates the application reminder sent status to True + operationId: api.routes.update_application_reminder_sent_status + parameters: + - $ref: "components.yml#/components/parameters/round_id" + - name: status + in: query + schema: + type: boolean + enum: + - true + required: true + responses: + 200: + description: Updated the application reminder sent status to True + + 400: + description: "Invalid round ID is supplied" + content: + texts/plain: + schema: + $ref: 'components.yml#/components/schemas/Error' + + /funds/{fund_id}/rounds/{round_id}/available_flag_allocations: + get: + tags: + - Static values + summary: Returns list of all available allocations for flags for this round + description: Returns list of all flag allocations + operationId: api.routes.get_available_flag_allocations + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + responses: + 200: + description: "List all available allocations for flags in this round." + content: + application/json: + schema: + type: array + 404: + description: "No flag allocations exist" + content: + texts/plain: + schema: + $ref: 'components.yml#/components/schemas/Error' + /funds/{fund_id}/rounds: + get: + tags: + - Rounds + summary: Given a fund ID we return all rounds for that fund. + description: Given a fund ID we return all rounds for that fund. + operationId: api.routes.get_rounds_for_fund + parameters: + - in: path + name: fund_id + schema: + type: string + required: true + - name: use_short_name + in: query + schema: + type: boolean + required: false + responses: + 200: + description: A list of rounds matching the given fund ID. + # content: + # application/json: + # schema: + # type: array + # items: + # $ref : 'components.yml#/components/schemas/Round' + 404: + description: "Rounds page not found for given fund id." + content: + application/json: + schema: + $ref: "components.yml#/components/schemas/Error" + /funds/{fund_id}/rounds/{round_id}/sections/application: + get: + tags: + - Sections + summary: Returns the application sections for the given round + description: Given a fund ID and a round ID we return the display sections for Application + operationId: api.routes.get_sections_for_round_application + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + - name: language + in: query + schema: + type: string + responses: + 200: + description: "If the round exists then the sections are returned." + 404: + description: "If an invalid fund/round ID is supplied" + /funds/{fund_id}/rounds/{round_id}/sections/assessment: + get: + tags: + - Sections + summary: Returns the assessment sections for the given round + description: Given a fund ID and a round ID we return the display sections for assessment + operationId: api.routes.get_sections_for_round_assessment + parameters: + - $ref: "components.yml#/components/parameters/fund_id" + - $ref: "components.yml#/components/parameters/round_id" + - name: language + in: query + schema: + type: string + responses: + 200: + description: "If the round exists then the sections are returned." + 404: + description: "If an invalid fund/round ID is supplied" + /events/{type}: + get: + summary: Returns all the event that are of a given type. + description: Returns all the event that are of a given type. + operationId: api.routes.get_events_by_type + parameters: + - name: type + in: path + schema: + type: string + required: true + responses: + 200: + description: "If there are events of the type then the data is returned." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Events' + 404: + description: "Event not found from given fund, round or event id." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Error' + /event: + post: + summary: Creates an event with the supplied parameters + description: Returns the event that has been created + operationId: api.routes.create_event + responses: + 201: + description: SUCCESS - Event created + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Event' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + round_id: + type: string + processed: + type: string + type: + type: string + activation_date: + type: string + format: date + /event/{event_id}: + get: + summary: Returns the event with the given event ID. + description: Returns the event with the given event ID. + operationId: api.routes.get_event_by_id + parameters: + - name: event_id + in: path + schema: + type: string + required: true + responses: + 200: + description: "If the event exists then the data is returned." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Event' + 404: + description: "Event not found from given event id." + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Error' + put: + summary: Updates the event + description: Updates the event to be marked as processed + operationId: api.routes.set_event_to_processed + parameters: + - name: event_id + in: path + schema: + type: string + required: true + - name: processed + in: query + schema: + type: boolean + required: true + responses: + 200: + description: Updates the event to be marked as processed + content: + application/json: + schema: + $ref: 'components.yml#/components/schemas/Event' + 400: + description: "Invalid fund, round or event ID supplied" + content: + texts/plain: + schema: + $ref: 'components.yml#/components/schemas/Error' +tags: + - name: Funds + description: "See all funds or find a fund" + - name: Rounds + description: "See all rounds or find a round" diff --git a/fund_store/openapi/components.yml b/fund_store/openapi/components.yml new file mode 100644 index 000000000..1332c1e0f --- /dev/null +++ b/fund_store/openapi/components.yml @@ -0,0 +1,144 @@ +components: + schemas: + Fund: + type: object + properties: + name: + type: string + description: The name of the fund. + example: "J's Sensible T-Shirt Fund" + description: + type: string + description: The description of the fund. + example: "Help J buy more t-shirt." + id: + type: string + description: The fund ID, uniquely identifies the fund. + example: "sdknbfs98yf48sfd" + required: + - id + - name + - description + AssessmentCriteriaWeighting: + type: array + properties: + strategy: + type: number + format: float + minimum: 0.0 + maximum: 1.0 + description: The score weighting for the strategy criteria + example: 0.4 + deliverability: + type: number + format: float + minimum: 0.0 + maximum: 1.0 + description: The score weighting for the deliverability criteria + example: 0.3 + value_for_money: + type: number + format: float + minimum: 0.0 + maximum: 1.0 + description: The score weighting for the value for money criteria + example: 0.3 + required: + - strategy + - deliverability + - value_for_money + Round: + type: object + properties: + id: + type: string + description: id of rounds. + example: "12345-qwert-....." + title: + type: string + description: title or name of the round. + example: "Spring" + fund_id: + type: string + description: The fund ID, uniquely identifies the fund. + example: "funding-service-design" + opens: + type: string + description: The date of when the round will be open to recieve applications. + example: "2022-12-25 00:00:00" + deadline: + type: string + description: The date of when the round will stop accepting applications. + example: "2022-12-25 00:00:00" + assessment_deadline: + type: string + description: The date when the assessments will be completed + example: "2022-12-25 00:00:00" + required: + - id + - fund_id + - title + - opens + - deadline + - assessment_deadline + Event: + type: object + properties: + id: + type: string + description: The id of the event. + example: "12345-qwert-....." + round_id: + type: string + nullable: true + description: The round ID, uniquely identifies the round. + example: "12345-qwert-....." + type: + type: string + description: the type of event. + example: "APPLICATION_DEADLINE_REMINDER" + activation_date: + type: string + description: The date after which the event is ready for processing + example: "2022-12-25 00:00:00" + processed: + type: string + format: string + nullable: true + description: Date of when the event has been processed, null otherwise + example: null + required: + - id + - round_id + - type + - activation_date + - processed + Events: + type: array + items: + $ref: '#/components/schemas/Event' + EoiDecisionSchema: + type: object + Error: + type: object + properties: + code: + type: integer + message: + type: string + required: + - code + - message + parameters: + fund_id: + in: path + name: fund_id + schema: + type: string + required: true + round_id: + in: path + name: round_id + schema: + type: string + required: true diff --git a/fund_store/pyproject.toml b/fund_store/pyproject.toml new file mode 100644 index 000000000..89e35d722 --- /dev/null +++ b/fund_store/pyproject.toml @@ -0,0 +1,61 @@ +[project] +name = "funding-service-design-fund-store" +version = "0.1.1" +description = "The funding service design fund store for the DLUHC." +authors = ["Version One", "HM Government, Department of Levelling Up, Housing and Communities"] +license = "MIT License" + +requires-python = ">=3.10, <3.11" +dependencies = [ + "airium>=0.2.6", + "bs4>=0.0.2", + "connexion[flask,swagger-ui,uvicorn]>=3.1.0", + "flask-json==0.4.0", + "flask-migrate==4.0.7", + "flask-sqlalchemy==3.1.1", + "flask==3.0.3", + "funding-service-design-utils>=5.0.8,<6.0.0", + "marshmallow-sqlalchemy==1.0.0", + "openapi-spec-validator>=0.7.1", + "prance>=23.6.21.0", + "psycopg2-binary==2.9.9", + "pytest-html>=3.2.0", + "pytest-mock==3.14.0", + "sqlalchemy-json==0.7.0", + "sqlalchemy-utils==0.41.2", + "sqlalchemy[mypy]>=2.0.30", + "swagger-ui-bundle==1.1.0", + "uvicorn==0.30.1", +] + +[tool.black] +line-length = 120 + +[tool.flake8] +max-line-length = 120 +ignore = ['E203', 'W503'] +count = true +exclude = ['config/fund_loader_config/FAB/*'] + +[tool.isort] +profile = "black" +force_single_line = "true" + +[tool.uv] + +[dependency-groups] +dev = [ + "asserts==0.11.1", + "black>=24.4.2", + "colored>=2.2.4", + "debugpy>=1.8.1", + "deepdiff>=7.0.1", + "flake8-pyproject>=1.2.3", + "invoke>=2.2.0", + "json2html==1.3.0", + "pre-commit~=4.0.0", + "pytest>=8.2.2", + "pytest-env>=1.1.3", + "pytest-flask>=1.3.0", + "pytest-mock==3.14.0", +] diff --git a/fund_store/scripts/__init__.py b/fund_store/scripts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fund_store/scripts/all_questions/README.md b/fund_store/scripts/all_questions/README.md new file mode 100644 index 000000000..3502a33ff --- /dev/null +++ b/fund_store/scripts/all_questions/README.md @@ -0,0 +1,29 @@ +# All Questions Generation +The scripts in this folder are used to generate the 'all questions' page for a fund. They take the section information from the fund-store database, combine it with the question flow from the form jsons, to generate a single HTML page containing all questions and all possible branches for a particular round. + +Instructions on running the script and what to do with the outputs are in confluence: https://dluhcdigital.atlassian.net/wiki/spaces/FS/pages/45580513/Generating+the+All+Questions+page+and+Assessment+Field+Display+info + + +## Generating test data +This is for testing [build_hierarchy_levels_for_page](./metadata_utils.py), or anything that takes in the full form json. You can use the full form json, or if you want to use a subset of the form json to make debugging easier, these instructions allow you to extract a small portion of the form for easier testing. + +These steps are to help with debugging by separating out the steps or just extracting a subset of the form. + +In order to make debugging easier, there are methods to extract a subset of data from a form, to test a particular combination of questions and conditions. You can also save the 'metadata' used for the hierarchy information to a file to again make debugging easier. + +### To generate meta data +1. Un-skip the test `test_generate_metadata` in [test_generate_all_questions.py](../../tests/test_generate_all_questions.py) +1. Update the `path_to_form` to the form you want metadata for +1. Execute this single test + +### To generate test data +If you are testing against metadata for the whole form, you don't need this bit, just the meta data. This allows you to generate metadata for a subset of a form, basically setting the start and end pages to somewhere in the middle of the form. +1. Generate metadata, as per above instructions +1. Look at the dict objects defined in [generate_test_data.py](./generate_test_data.py) - these are how you define the subset of the form you want to test. Update the start and end paths for the subset you want, and include every path you want in between the in the `pages` list. +1. Unskip `test_generate_test_data` in [test_generate_all_questions.py](../../tests/test_generate_all_questions.py) +1. Update the out path and input path (uses the metadata_path from above by default) +1. Update the `files_to_generate` path to use the dict you defined above. +1. Execute this single test + +### Testing build_hierarchy_levels_for_page +You can now write a test using `build_hierarchy_levels_for_page` with either a whole form of metadata, or just a subset. Use the output paths above as input to your test. diff --git a/fund_store/scripts/all_questions/__init__.py b/fund_store/scripts/all_questions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fund_store/scripts/all_questions/generate_test_data.py b/fund_store/scripts/all_questions/generate_test_data.py new file mode 100644 index 000000000..0b2ada21d --- /dev/null +++ b/fund_store/scripts/all_questions/generate_test_data.py @@ -0,0 +1,56 @@ +import json +import os + +START_TO_MAIN_ACTIVITIES = { + "file_name": "start_to_main_activites.json", + "start_page": "/intro-about-your-organisation", + "end_page": "/tell-us-about-your-organisations-main-activities", + "pages": [ + "/intro-about-your-organisation", + "/alternative-organisation-name", + "/organisation-details", + "/tell-us-about-your-organisations-main-activities", + ], +} + +HOW_IS_ORG_CLASSIFIED = { + "file_name": "how_is_org_classified.json", + "start_page": "/how-is-your-organisation-classified", + "end_page": "/organisation-address", + "pages": [ + "/how-is-your-organisation-classified", + "/how-is-your-organisation-classified-other", + "/charity-number", + "/company-registration-number", + "/organisation-address", + ], +} +JOINT_BID = { + "file_name": "joint_bid_out_and_back.json", + "start_page": "/joint-bid", + "end_page": "/website-and-social-media", + "pages": [ + "/partner-organisation-details", + "/work-with-partner-organisations", + "/agreement-exists", + "/website-and-social-media", + "/joint-bid", + ], +} + + +def generate_test_data(target_test_files: [], in_path: str, out_folder: str): + with open(in_path, "r") as f: + all_data = json.load(f) + + for file in target_test_files: + cutdown_data = {"start_page": file["start_page"], "all_pages": []} + for p in file["pages"]: + cutdown_data["all_pages"].append(next(page for page in all_data["all_pages"] if page["path"] == p)) + + last_page = next(p for p in cutdown_data["all_pages"] if p["path"] == file["end_page"]) + last_page["next_paths"] = [] + last_page["all_possible_after"] = [] + + with open(os.path.join(out_folder, file["file_name"]), "w") as f_out: + json.dump(cutdown_data, f_out) diff --git a/fund_store/scripts/all_questions/metadata_utils.py b/fund_store/scripts/all_questions/metadata_utils.py new file mode 100644 index 000000000..9ea383b8f --- /dev/null +++ b/fund_store/scripts/all_questions/metadata_utils.py @@ -0,0 +1,692 @@ +import copy +import fnmatch +import json +import os +from typing import Tuple + +from bs4 import BeautifulSoup, NavigableString + +from db.models.section import Section +from scripts.all_questions.read_forms import ( + build_section_header, + determine_display_value_for_condition, + determine_if_just_html_page, + increment_lowest_in_hierarchy, + remove_lowest_in_hierarchy, + strip_leading_numbers, +) + +FIELD_TYPES_WITH_MAX_WORDS = ["freetextfield", "multilinetextfield"] + + +def get_all_child_nexts(page: dict, child_nexts: list, all_pages: dict): + """Recursively builds a list of everything that could come next from this page, + and then everything that could come next from those next pages, and so on. + + + Args: + page (dict): _description_ + child_nexts (list): _description_ + all_pages (dict): _description_ + """ + # TODO write tests + child_nexts.update([n for n in page["next_paths"]]) + for next_page_path in page["next_paths"]: + next_page = next(p for p in all_pages if p["path"] == next_page_path) + get_all_child_nexts(next_page, child_nexts, all_pages) + + +def get_all_possible_previous(page_path: str, results: list, all_pages: dict): + """Recursively finds all pages that could have come before this one, in any branch of questions + + Args: + page_path (str): _description_ + results (list): _description_ + all_pages (dict): _description_ + """ + + # TODO write tests + direct_prev = [prev["path"] for prev in all_pages if page_path in prev["next_paths"]] + results.update(direct_prev) + for prev in direct_prev: + get_all_possible_previous(prev, results, all_pages) + + +def generate_metadata(full_form_data: dict) -> dict: + """Generates metadata for a form. Basically a dict containing the following: + ``` + { + "start_page": "/intro-about-your-organisation", + "all_pages": [ + { + "path": "/organisation-details", + "next_paths": [ + Everything that could come directly after this page + ], + "all_direct_previous": [ + Everything that could come directly (immediately) before this page + ], + "direct_next_of_direct_previous": [ + Everything that could come immediately after the pages that lead directly to this one + (ie this pages siblings) + ], + "all_possible_next_of_siblings": [ + Everything that could come any point after any of this pages siblings + ], + "all_possible_previous": [ + Everthing that could come at any point before this page + ], + "all_possible_previous_direct_next": [ + Everything that could come anywhere before the pages that come directly after this one + ], + "all_possible_after": [ + Everything that could come anywhere after this page + ] + }, + ] + ``` + + Args: + full_form_data (dict): Data from the form json file + + Returns: + dict: The metadata, as described above + """ + + cutdown = {"start_page": full_form_data["startPage"], "all_pages": []} + for page in full_form_data["pages"]: + cp = {"path": page["path"], "next_paths": [p["path"] for p in page["next"]]} + cutdown["all_pages"].append(cp) + + metadata = copy.deepcopy(cutdown) + for p in metadata["all_pages"]: + # everything that could come immediately before this page + p["all_direct_previous"] = [prev["path"] for prev in cutdown["all_pages"] if p["path"] in prev["next_paths"]] + + # all the immediate next paths of the direct previous (aka siblings) + direct_next_of_direct_previous = set() + for direct_prev in p["all_direct_previous"]: + prev_page = next(prev for prev in cutdown["all_pages"] if prev["path"] == direct_prev) + direct_next_of_direct_previous.update(prev_page["next_paths"]) + p["direct_next_of_direct_previous"] = list(direct_next_of_direct_previous) + + # get all the descendents (possible next anywhere after) of the siblings + all_possible_next_of_siblings = set() + for sibling in p["direct_next_of_direct_previous"]: + sibling_page = next(page for page in cutdown["all_pages"] if page["path"] == sibling) + get_all_child_nexts(sibling_page, all_possible_next_of_siblings, cutdown["all_pages"]) + p["all_possible_next_of_siblings"] = list(all_possible_next_of_siblings) + + # everything that could come anywhere before this page + all_possible_previous = set() + get_all_possible_previous(p["path"], all_possible_previous, cutdown["all_pages"]) + p["all_possible_previous"] = list(all_possible_previous) + + # get everything that is directly after all the possible previous to this page + all_possible_previous_direct_next = set() + for prev in p["all_possible_previous"]: + prev_page = next(page for page in cutdown["all_pages"] if page["path"] == prev) + all_possible_previous_direct_next.update(prev_page["next_paths"]) + p["all_possible_previous_direct_next"] = list(all_possible_previous_direct_next) + + # everything that could come after this page + all_possible_after = set() + get_all_child_nexts(page=p, child_nexts=all_possible_after, all_pages=cutdown["all_pages"]) + p["all_possible_after"] = list(all_possible_after) + + return metadata + + +def build_hierarchy_levels_for_page(page: dict, results: dict, idx: int, all_pages: dict, start_page: bool = False): + """Recursively builds up a dict containing the path of each page, and it's level in the hierarchy of the page + Format of results: + ``` + { + "/path-to-page-1": 1, + "/path-to-sub-page": 2, + "/path-to-page-2": 1, + "/path-to-another-sub-page": 2, + } + ``` + + Args: + page (dict): Page object from metadata + results (dict): The dict that will store the hierarchy results + idx (int): The hierarchy level of this page at this point in the tree + all_pages (dict): All the pages in the form + start_page (bool, optional): Whether or not this is the first page in the form. Defaults to False. + """ + current_level_in_results = results.get(page["path"], 9999) + # We want the lowest level the page appears at, so only update if we are at it's lowest point + if idx < current_level_in_results: + results[page["path"]] = idx + + # loop through every page that comes after this page + for next_path in [n for n in page["next_paths"]]: + # default is same level + next_idx = idx + next_page = next(p for p in all_pages if p["path"] == next_path) + + # if we have more than one possible next page, go to next level + if len(page["next_paths"]) > 1 or start_page is True: + next_idx = idx + 1 + + # if this next path is also a next path of the immediate previous, go back a level + elif next_path in page["direct_next_of_direct_previous"]: + next_idx = idx - 1 + + elif next_path in page["all_possible_previous_direct_next"]: + next_idx = idx - 1 + + # if this page and all it's siblings eventually go back to this same next page, go back a level + elif len(page["direct_next_of_direct_previous"]) <= 1: + pass + elif len(next_page["all_direct_previous"]) == 1: + pass + else: + # Determine whether this next page is the return point for this page and all it's siblings + is_in_descendents_of_all_siblings = True + for sibling in page["direct_next_of_direct_previous"]: + # don't look at this page + if sibling == page["path"]: + continue + sibling_page = next(p for p in all_pages if p["path"] == sibling) + if next_path not in sibling_page["all_possible_after"]: + is_in_descendents_of_all_siblings = False + + if is_in_descendents_of_all_siblings: + next_idx = idx - 1 + + build_hierarchy_levels_for_page(next_page, results, next_idx, all_pages) + + +def strip_string_and_append_if_not_empty(string_to_check: str, list_to_append: list): + """Uses `str.strip()` to remove leading/trailing whitespace from `string_to_check`. + If the resulting string is not empty, appends this to `list_to_append` + + Args: + string_to_check (str): String to strip and append + list_to_append (list): List to append to + """ + stripped = string_to_check.strip() + if stripped: + list_to_append.append(stripped) + + +def extract_from_html(soup, results: list): + """ + Takes in a BeautifulSoup element, recursively iterates through it's children to generate text items + for rendering in the all questions page. + + Any non-empty strings are stripped of leading/trailing spaces etc and appended to `results`. + AnyInclude any activities you undertake in order to achieve the organisation's purpose.
\n\nYou must list at least one, and can include up to three
A registered community interest company is a special type of limited company. It exists to benefit the community rather than private shareholders.
\nA social enterprise is a business with social objectives. It reinvests its surpluses in the business or community, rather than driving the need to maximise profit for shareholders and owners.
\nSelect one option
", + "list": "jcXaBR" + } + ], + "next": [ + { + "path": "/how-is-your-organisation-classified-other", + "condition": "aJJVVz" + }, + { + "path": "/charity-number", + "condition": "aVVJJz" + }, + { + "path": "/company-registration-number" + } + ], + "section": "uLwBuz" + }, + { + "title": "How is your organisation classified? (other)", + "path": "/how-is-your-organisation-classified-other", + "components": [ + { + "name": "jemcJE", + "options": { + "classes": "govuk-input", + "hideTitle": true + }, + "type": "TextField", + "title": "How is your organisation classified?", + "schema": {} + } + ], + "next": [ + { + "path": "/organisation-address" + } + ], + "section": "uLwBuz" + }, + { + "title": "Company registration number (optional)", + "path": "/company-registration-number", + "components": [ + { + "name": "jcjKhd", + "options": {}, + "type": "Para", + "content": "If you have a registration number, you can find this by searching Companies House.
It is usually 8 numbers, or 2 letters followed by 6 numbers
You can find this by searching the register of charities on the Charity Commission website.
It is usually 7 numbers, or 8 numbers if your charity is recently registered
You can add more partner organisations on the next step
For example, your company's Facebook, Instagram or Twitter accounts (if applicable)
You can add more links on the next step
", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/summary" + } + ], + "controller": "RepeatingFieldPageController", + "section": "uLwBuz" + }, + { + "path": "/summary", + "title": "Check your answers", + "components": [], + "next": [], + "section": "uLwBuz", + "controller": "./pages/summary.js" + }, + { + "path": "/intro-about-your-organisation", + "title": "2.1 About your organisation", + "section": "uLwBuz", + "components": [ + { + "name": "sPbqXm", + "options": {}, + "type": "Html", + "content": "In this section, we'll ask about:
Explain what their role is in the delivery of the project and how they'll assist with delivery
" + } + ], + "next": [ + { + "path": "/agreement-exists" + } + ], + "section": "uLwBuz" + }, + { + "path": "/agreement-exists", + "title": "Does an agreement currently exist between your organisation and the partnership organisations?", + "components": [ + { + "name": "RUdOhN", + "options": { + "hideTitle": true + }, + "type": "YesNoField", + "title": "Does an agreement currently exist between your organisation and the partnership organisations?", + "hint": "We may ask for a copy of the agreement if your application is successful
", + "schema": {} + } + ], + "next": [ + { + "path": "/website-and-social-media" + } + ], + "section": "uLwBuz" + } + ], + "lists": [ + { + "title": "Organisation classification", + "name": "jcXaBR", + "type": "string", + "items": [ + { + "text": "Upper or lower tier local authority", + "value": "upper-or-lower-tier-local-authority" + }, + { + "text": "Charity with a registered charity number", + "value": "charity-with-a-registered-charity-number" + }, + { + "text": "Registered community interest company", + "value": "registered-community-interest-company" + }, + { + "text": "Social enterprise", + "value": "social-enterprise" + }, + { + "text": "Community group", + "value": "community-group" + }, + { + "text": "Other", + "value": "other" + } + ] + }, + { + "title": "Focus of the project", + "name": "hfQISg", + "type": "string", + "items": [ + { + "text": "Trauma support", + "value": "trauma-support" + }, + { + "text": "Mental health and wellbeing", + "value": "mental-health-and-wellbeing" + }, + { + "text": "Teaching English as a language", + "value": "teaching-english-as-a-language" + }, + { + "text": "Peer groups and befriending", + "value": "peer-groups-and-befriending" + }, + { + "text": "Supporting hosts and sponsors", + "value": "supporting-hosts-and-sponsors" + }, + { + "text": "Maintaining links to culture", + "value": "maintaining-links-to-culture" + } + ] + }, + { + "title": "Cohort focus", + "name": "YSgfSN", + "type": "string", + "items": [ + { + "text": "Ukrainian schemes", + "value": "ukrainian-schemes" + }, + { + "text": "Hong Kong British Nationals (Overseas)", + "value": "hong-kong-british-nationals" + }, + { + "text": "Afghan citizens resettlement scheme", + "value": "afghan-citizens-resettlement-scheme" + } + ] + }, + { + "title": "Eligibility", + "name": "qwnTyS", + "type": "string", + "items": [ + { + "text": "Upper or lower tier local authority", + "value": "1" + }, + { + "text": "Charity with a registered charity number", + "value": "2" + }, + { + "text": "Registered community interest company", + "value": "3" + }, + { + "text": "Social enterprise", + "value": "4" + }, + { + "text": "Community group", + "value": "5" + }, + { + "text": "None of these", + "value": "6", + "description": "" + } + ] + } + ], + "sections": [ + { + "name": "uLwBuz", + "title": "2.1 About your organisation" + } + ], + "conditions": [ + { + "displayName": "org other name yes", + "name": "aHJVVz", + "value": { + "name": "org other name yes", + "conditions": [ + { + "field": { + "name": "KUdOhN", + "type": "YesNoField", + "display": "Does your organisation use any other names?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "org other yes", + "name": "aJJVVz", + "value": { + "name": "org other yes", + "conditions": [ + { + "field": { + "name": "jcmcJE", + "type": "RadiosField", + "display": "How is your organisation classified?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "other", + "display": "other" + } + } + ] + } + }, + { + "displayName": "org charity yes", + "name": "aVVJJz", + "value": { + "name": "org charity yes", + "conditions": [ + { + "field": { + "name": "jcmcJE", + "type": "RadiosField", + "display": "How is your organisation classified?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "charity-with-a-registered-charity-number", + "display": "charity" + } + } + ] + } + }, + { + "displayName": "org company yes", + "name": "aVJVJz", + "value": { + "name": "org company yes", + "conditions": [ + { + "field": { + "name": "jcmcJE", + "type": "RadiosField", + "display": "How is your organisation classified?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "registered-community-interest-company", + "display": "company" + } + } + ] + } + }, + { + "displayName": "Org other name no", + "name": "tOevBV", + "value": { + "name": "Org other name no", + "conditions": [ + { + "field": { + "name": "KUdOhN", + "type": "YesNoField", + "display": "Does your organisation use any other names?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "joint bid yes", + "name": "nMJAdu", + "value": { + "name": "joint bid yes", + "conditions": [ + { + "field": { + "name": "MRdGKt", + "type": "YesNoField", + "display": "Is your application a joint bid in partnership with other organisations?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "joint bid no", + "name": "ZhSWyC", + "value": { + "name": "joint bid no", + "conditions": [ + { + "field": { + "name": "MRdGKt", + "type": "YesNoField", + "display": "Is your application a joint bid in partnership with other organisations?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Ukraine support", + "name": "btRdYE", + "value": { + "name": "Ukraine support", + "conditions": [ + { + "field": { + "name": "vYYoAC", + "type": "CheckboxesField", + "display": "Which cohort will your project focus on?" + }, + "operator": "contains", + "value": { + "type": "Value", + "value": "ukrainian-schemes", + "display": "ukrainian-schemes" + } + } + ] + } + }, + { + "displayName": "Organisation no", + "name": "dfsBce", + "value": { + "name": "Organisation no", + "conditions": [ + { + "field": { + "name": "KUdOhN", + "type": "YesNoField", + "display": "Does your organisation use any other names?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Organisation yes", + "name": "GZIoHg", + "value": { + "name": "Organisation yes", + "conditions": [ + { + "field": { + "name": "KUdOhN", + "type": "YesNoField", + "display": "Does your organisation use any other names?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Not a joint bid", + "name": "MbgmFQ", + "value": { + "name": "Not a joint bid", + "conditions": [ + { + "field": { + "name": "MRdGKt", + "type": "YesNoField", + "display": "Is your application a joint bid in partnership with other organisations?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Based in england no", + "name": "JZGvHK", + "value": { + "name": "Based in england no", + "conditions": [ + { + "field": { + "name": "qGbSWS", + "type": "YesNoField", + "display": "Is your organisation based in England?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Take place in England no", + "name": "LOPfHT", + "value": { + "name": "Take place in England no", + "conditions": [ + { + "field": { + "name": "mknqUV", + "type": "YesNoField", + "display": "Will the project take place in England?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + } + ], + "fees": [], + "outputs": [ + { + "name": "update-form", + "title": "Update form in application store", + "type": "savePerPage", + "outputConfiguration": { + "savePerPageUrl": "True" + } + } + ], + "version": 2, + "skipSummary": false, + "name": "Apply for funding to support children and young people on pathways to the UK from Ukraine, Hong Kong and Afghanistan" +} diff --git a/fund_store/tests/test_data/all_questions/forms/name-your-application.json b/fund_store/tests/test_data/all_questions/forms/name-your-application.json new file mode 100644 index 000000000..fc203e4bd --- /dev/null +++ b/fund_store/tests/test_data/all_questions/forms/name-your-application.json @@ -0,0 +1,59 @@ +{ + "metadata": {}, + "startPage": "/11-name-your-application", + "pages": [ + { + "title": "1.1 Name your application", + "path": "/11-name-your-application", + "components": [ + { + "name": "JAAhRP", + "options": { + "hideTitle": true, + "classes": "govuk-!-width-full" + }, + "type": "TextField", + "title": "Name your application", + "hint": "In this section, we'll ask about:
Tell us about:
\n\nYou can add more risks on the next step.
\n\n\n\n" + }, + { + "name": "qQLYzL", + "options": { + "columnTitles": [ + "Risk", + "Likelihood", + "Impact", + "Proposed mitigation", + "Action" + ], + "required": true + }, + "type": "MultiInputField", + "title": "Risks to the project", + "hint": "The MultiInputField needed", + "schema": {}, + "children": [ + { + "name": "CzoasH", + "options": { + "maxWords": "500" + }, + "type": "MultilineTextField", + "title": "Risk", + "schema": {} + }, + { + "name": "eADHGN", + "options": {}, + "type": "RadiosField", + "title": "Likelihood", + "list": "zOwfxe" + }, + { + "name": "MPHvIr", + "options": {}, + "type": "RadiosField", + "title": "Impact", + "list": "yOwfxe" + }, + { + "name": "SKQluJ", + "options": { + "maxWords": "500" + }, + "type": "MultilineTextField", + "title": "Proposed mitigation", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/who-is-responsible-risk-register" + } + ], + "section": "hhbMar", + "controller": "RepeatingFieldPageController" + }, + { + "path": "/who-is-responsible-risk-register", + "title": "Who owns the overall risk register?", + "components": [ + { + "name": "KHESdE", + "options": { + "hideTitle": true + }, + "type": "TextField", + "title": "Who owns the overall risk register?", + "hint": "Include members of your board, their roles and main responsibilities
", + "schema": {} + } + ], + "next": [ + { + "path": "/summary" + } + ], + "section": "hhbMar" + } + ], + "lists": [ + { + "title": "Likelihood", + "name": "zOwfxe", + "type": "string", + "items": [ + { + "text": "High", + "value": "High" + }, + { + "text": "Medium", + "value": "Medium" + }, + { + "text": "Low", + "value": "Low" + } + ] + }, + { + "title": "Impact", + "name": "yOwfxe", + "type": "string", + "items": [ + { + "text": "High", + "value": "High" + }, + { + "text": "Medium", + "value": "Medium" + }, + { + "text": "Low", + "value": "Low" + } + ] + } + ], + "sections": [ + { + "name": "hhbMar", + "title": "5.1 Risk and deliverability" + } + ], + "conditions": [], + "fees": [], + "outputs": [ + { + "name": "update-form", + "title": "Update form in application store", + "type": "savePerPage", + "outputConfiguration": { + "savePerPageUrl": "True" + } + } + ], + "version": 2, + "skipSummary": false, + "name": "Apply for funding to support children and young people on pathways to the UK from Ukraine, Hong Kong and Afghanistan" +} diff --git a/fund_store/tests/test_data/all_questions/forms/your-skills-and-experience-dpi.json b/fund_store/tests/test_data/all_questions/forms/your-skills-and-experience-dpi.json new file mode 100644 index 000000000..1e4abd593 --- /dev/null +++ b/fund_store/tests/test_data/all_questions/forms/your-skills-and-experience-dpi.json @@ -0,0 +1,147 @@ +{ + "metadata": {}, + "startPage": "/31-your-skills-and-experience", + "pages": [ + { + "title": "3.1 Your skills and experience", + "path": "/31-your-skills-and-experience", + "components": [ + { + "name": "MbtXtU", + "options": {}, + "type": "Html", + "content": "In this section, we'll ask about:
\nadsfasdfasdf
\r\nneed some support with filling out the form
" + } + ] + }, + { + "category": "IEwDfr", + "question": "Describe your project and its aims", + "fields": [ + { + "key": "MxzEYq", + "title": "Describe your project and its aims", + "type": "freeText", + "answer": "To build stuff
" + } + ] + }, + { + "category": "IEwDfr", + "question": "Lead contact details", + "fields": [ + { + "key": "xWnVof", + "title": "Name of lead contact", + "type": "text", + "answer": "Bandit Healer" + }, + { + "key": "NQoGIm", + "title": "Lead contact email address", + "type": "text", + "answer": "bandit@bluey.com" + }, + { + "key": "srxZmv", + "title": "Lead contact telephone number", + "type": "text", + "answer": "0123123123" + } + ] + } + ], + "metadata": { + "isSummaryPageSubmit": true, + "paymentSkipped": false + } + }, + { + "name": "Access funding cof-eoi-asset", + "questions": [ + { + "category": "McpePG", + "question": "Does your organisation plan both to receive the funding and run the project?", + "fields": [ + { + "key": "eEaDGz", + "title": "Does your organisation plan both to receive the funding and run the project?", + "type": "list", + "answer": true + } + ] + }, + { + "category": "McpePG", + "question": "Type of asset", + "fields": [ + { + "key": "Ihjjyi", + "title": "Type of asset", + "type": "list", + "answer": "Community centre" + } + ] + }, + { + "category": "McpePG", + "question": "Is the asset based in the UK?", + "fields": [ + { + "key": "zurxox", + "title": "Is the asset based in the UK?", + "type": "list", + "answer": true + } + ] + }, + { + "category": "McpePG", + "question": "Address of the asset", + "fields": [ + { + "key": "dnqIdW", + "title": "Address of the asset", + "type": "text", + "answer": "asdf, null, asdf, null, NP11 3RD" + } + ] + }, + { + "category": "McpePG", + "question": "Is the asset at risk?", + "fields": [ + { + "key": "lLQmNb", + "title": "Is the asset at risk?", + "type": "list", + "answer": true + } + ] + }, + { + "category": "McpePG", + "question": "What is the risk to the asset?", + "fields": [ + { + "key": "ilMbMH", + "title": "What is the risk to the asset?", + "type": "list", + "answer": [ + "Closure or end of lease" + ] + } + ] + }, + { + "category": "McpePG", + "question": "Has the asset ever been used by or had significance to the community?", + "fields": [ + { + "key": "fBhSNc", + "title": "Has the asset ever been used by or had significance to the community?", + "type": "list", + "answer": true + } + ] + }, + { + "category": "McpePG", + "question": "Do you already own the asset?", + "fields": [ + { + "key": "cPcZos", + "title": "Do you already own the asset?", + "type": "list", + "answer": false + } + ] + }, + { + "category": "McpePG", + "question": "Does the asset belong to a public authority?", + "fields": [ + { + "key": "XuAyrs", + "title": "Does the asset belong to a public authority?", + "type": "list", + "answer": "Yes, a town, parish or community council" + } + ] + }, + { + "category": "McpePG", + "question": "Select the option which best represents the stage you are at in purchasing the asset", + "fields": [ + { + "key": "oDhZlw", + "title": "Stage purchasing asset", + "type": "list", + "answer": "Do not know who owner is" + } + ] + } + ], + "metadata": { + "isSummaryPageSubmit": true, + "paymentSkipped": false + } + } +] diff --git a/fund_store/tests/test_data/test_fab_round_config.py b/fund_store/tests/test_data/test_fab_round_config.py new file mode 100644 index 000000000..d4ae85d80 --- /dev/null +++ b/fund_store/tests/test_data/test_fab_round_config.py @@ -0,0 +1,63 @@ +LOADER_CONFIG = { + "base_path": 0, + "sections_config": [ + {"section_name": {"en": "1. Test Section", "cy": ""}, "tree_path": "0.1.1", "requires_feedback": None}, + { + "section_name": {"en": "1.1 Name your application", "cy": ""}, + "tree_path": "0.1.1.1", + "form_name_json": {"en": "organisation-information", "cy": ""}, + }, + {"section_name": {"en": "2. Test Section 2", "cy": ""}, "tree_path": "0.1.2", "requires_feedback": None}, + { + "section_name": {"en": "2.1 Asset information", "cy": ""}, + "tree_path": "0.1.2.1", + "form_name_json": {"en": "asset-information-cof-r3-w2", "cy": ""}, + }, + ], + "fund_config": { + "id": "b33ea578-b35b-4508-994a-7589a9c9c060", + "short_name": "T", + "welsh_available": False, + "owner_organisation_name": "", + "owner_organisation_shortname": "", + "owner_organisation_logo_uri": "", + "name_json": {"en": "Test"}, + "title_json": {"en": "Test Fund"}, + "description_json": {"en": "test"}, + "ggis_scheme_reference_number": "", + }, + "round_config": { + "id": "1a2d6043-689b-4472-a09c-fd8fcfd20151", + "fund_id": "b33ea578-b35b-4508-994a-7589a9c9c060", + "short_name": "T", + "opens": "2025-12-12T12:00:00", + "assessment_start": "2026-01-12T12:00:00", + "deadline": "2025-12-24T12:00:00", + "application_reminder_sent": False, + "reminder_date": "2026-01-23T12:00:00", + "assessment_deadline": "2026-01-24T12:00:00", + "prospectus": "https://www.google.com", + "privacy_notice": "https://www.google.com", + "reference_contact_page_over_email": False, + "contact_email": "test@hotmail.com", + "contact_phone": "07555094188", + "contact_textphone": "07555094188", + "support_times": "no", + "support_days": "Mon", + "instructions_json": {"en": "None", "cy": None}, + "feedback_link": "https://www.google.com", + "project_name_field_id": "Test", + "application_guidance_json": {"en": "None", "cy": None}, + "guidance_url": "https://www.google.com", + "all_uploaded_documents_section_available": False, + "application_fields_download_available": False, + "display_logo_on_pdf_exports": False, + "mark_as_complete_enabled": False, + "is_expression_of_interest": False, + "eoi_decision_schema": None, + "feedback_survey_config": "None", + "eligibility_config": {"has_eligibility": False}, + "title_json": {"en": "Tree"}, + "contact_us_banner_json": {"en": "None", "cy": None}, + }, +} diff --git a/fund_store/tests/test_data_fund_round.py b/fund_store/tests/test_data_fund_round.py new file mode 100644 index 000000000..89347ee4e --- /dev/null +++ b/fund_store/tests/test_data_fund_round.py @@ -0,0 +1,86 @@ +import pytest + +from db.models.fund import FundingType +from db.queries import ( + get_all_funds, + get_fund_by_id, + get_fund_by_short_name, + get_round_by_id, + get_round_by_short_name, + get_rounds_for_fund_by_id, + get_rounds_for_fund_by_short_name, +) + + +def test_get_fund_by_id(seed_dynamic_data): + f = get_fund_by_id(seed_dynamic_data["funds"][0]["id"]) + assert f.name_json["en"] == "Unit Test Fund 1" + assert f.short_name == "FND1" + assert hasattr(f, "ggis_scheme_reference_number") + assert f.ggis_scheme_reference_number is None + + +def test_get_fund_by_short_name(seed_dynamic_data): + f = get_fund_by_short_name("FND1") + assert f.name_json["en"] == "Unit Test Fund 1" + assert f.short_name == "FND1" + + +def test_get_round_by_id(seed_dynamic_data): + r = get_round_by_id( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][0]["id"], + ) + assert r.title_json["en"] == "Unit Test Round 1" + assert r.short_name == "RND1" + assert str(r.id) == seed_dynamic_data["funds"][0]["rounds"][0]["id"] + + +def test_get_rounds_for_fund_by_id(seed_dynamic_data): + r = get_rounds_for_fund_by_id(seed_dynamic_data["funds"][0]["id"]) + assert len(r) == 2 + # assert r[0].title_json["en"] == "Unit Test Round 1" + assert r[0].short_name == "RND1" + assert r[1].short_name == "RND2" + assert str(r[0].id) == seed_dynamic_data["funds"][0]["rounds"][0]["id"] + + +def test_get_rounds_for_fund_by_short_name(seed_dynamic_data): + r = get_rounds_for_fund_by_short_name("FND1") + assert len(r) == 2 + # assert r[0].title_json["en"] == "Unit Test Round 1" + assert r[0].short_name == "RND1" + assert r[1].short_name == "RND2" + assert str(r[0].id) == seed_dynamic_data["funds"][0]["rounds"][0]["id"] + + +@pytest.mark.parametrize( + "fund_short_name, round_short_name, expected_none, expected_title", + [ + ("FND1", "RND1", False, "Unit Test Round 1"), + ("fnd1", "rnd1", False, "Unit Test Round 1"), + ("bad", "rnd1", True, None), + ("fund", "bad", True, None), + ("FND1", "RND2", False, "Unit Test Round 2"), + ], +) +def test_get_round_by_short_name( + fund_short_name, + round_short_name, + expected_none, + expected_title, + seed_dynamic_data, +): + r = get_round_by_short_name(fund_short_name, round_short_name) + + if expected_none: + assert r is None + else: + assert r.title_json["en"] == expected_title + + +def test_get_all_funds(seed_dynamic_data): + result = get_all_funds() + assert len(result) == 1 + assert result[0].name_json["en"] == "Unit Test Fund 1" + assert result[0].funding_type == FundingType.COMPETITIVE diff --git a/fund_store/tests/test_data_sections.py b/fund_store/tests/test_data_sections.py new file mode 100644 index 000000000..96f1254c4 --- /dev/null +++ b/fund_store/tests/test_data_sections.py @@ -0,0 +1,64 @@ +from typing import List + +import pytest + +from config.fund_loader_config.cof.cof_r2 import ( + APPLICATION_BASE_PATH, + ASSESSMENT_BASE_PATH, + COF_ROUND_2_WINDOW_2_ID, + cof_r2_sections, + fund_config, + rounds_config, +) +from db.models.section import Section +from db.queries import ( + get_application_sections_for_round, + get_assessment_sections_for_round, + insert_base_sections, + insert_fund_data, + insert_or_update_application_sections, + upsert_round_data, +) + + +def test_get_application_sections(seed_dynamic_data): + sections: List[Section] = get_application_sections_for_round( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][0]["id"], + ) + assert len(sections) == 3 + first, second, third = sections + assert first.title_json == {"en": "Middle1"} + assert len(first.children) == 1 + assert second.title_json == {"en": "Middle2"} + assert len(second.children) == 1 + assert third.title_json == {"en": "skills"} + assert len(third.children) == 0 + + +def test_get_assessment_sections(seed_dynamic_data): + sections: List[Section] = get_assessment_sections_for_round( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][0]["id"], + "", + ) + assert len(sections) == 1 + assert sections[0].title_json == {"en": "assess section 1"} + assert len(sections[0].children) == 1 + assert sections[0].children[0].title_json == {"en": "assess section 1 a"} + assert len(sections[0].children[0].children) == 0 + + +def test_load_application_sections(clear_test_data): + insert_fund_data(fund_config) + upsert_round_data(rounds_config) + + base_sections = insert_base_sections(APPLICATION_BASE_PATH, ASSESSMENT_BASE_PATH, COF_ROUND_2_WINDOW_2_ID) + application_sections = insert_or_update_application_sections(COF_ROUND_2_WINDOW_2_ID, cof_r2_sections) + assert len(base_sections) == 2 + assert len(application_sections) == 28 + + +@pytest.mark.skip(reason="not implemented assessment loading yet") +def test_load_assessment_sections(): + pass diff --git a/fund_store/tests/test_data_update_scripts.py b/fund_store/tests/test_data_update_scripts.py new file mode 100644 index 000000000..f36718ace --- /dev/null +++ b/fund_store/tests/test_data_update_scripts.py @@ -0,0 +1,87 @@ +from scripts.data_updates.FS2910_ns_links import update_rounds_with_links +from scripts.data_updates.FS2956_ns_weightings import update_section_weightings +from scripts.data_updates.patch_cyp_name import update_fund_name + +from db.queries import get_application_sections_for_round, get_fund_by_id, get_round_by_id +from db.schemas.fund import FundSchema +from db.schemas.round import RoundSchema +from db.schemas.section import SectionSchema + + +def test_update_section_weightings(seed_dynamic_data): + sections = get_application_sections_for_round( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][0]["id"], + ) + section_to_update = None + for s in sections: + if "skill" in s.title_json["en"]: + section_to_update = s + assert section_to_update is not None, "Unable to find expected test data before updates" + + section_data = SectionSchema().dump(section_to_update) + section_data["tree_path"] = section_to_update.path + section_data["section_name"] = section_to_update.title_json + section_data["weighting"] = "12" + update_section_weightings(section_data) + + sections = get_application_sections_for_round( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][0]["id"], + ) + + for s in sections: + if "skill" in s.title_json["en"]: + section = s + assert section.weighting == 12 + + +def test_update_links_present(seed_dynamic_data): + r = get_round_by_id( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][0]["id"], + ) + round_data = RoundSchema().dump(r) + round_data["privacy_notice"] = "new privacy notice" + round_data["prospectus"] = "new prospectus" + + update_rounds_with_links([round_data]) + + r = get_round_by_id( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][0]["id"], + ) + + assert r.privacy_notice == "new privacy notice" + assert r.prospectus == "new prospectus" + + +def test_update_links_not_present(seed_dynamic_data): + r = get_round_by_id( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][1]["id"], + ) + round_data = RoundSchema().dump(r) + round_data["privacy_notice"] = "" + round_data["prospectus"] = "" + + update_rounds_with_links([round_data]) + + r = get_round_by_id( + seed_dynamic_data["funds"][0]["id"], + seed_dynamic_data["funds"][0]["rounds"][1]["id"], + ) + + assert r.privacy_notice == "http://google.com" + assert r.prospectus == "http://google.com" + + +def test_update_fund_name(seed_dynamic_data): + f = get_fund_by_id(seed_dynamic_data["funds"][0]["id"]) + fund_data = FundSchema().dump(f) + fund_data["name_json"] = "new name json" + + update_fund_name(fund_config=fund_data) + + f = get_fund_by_id(seed_dynamic_data["funds"][0]["id"]) + assert f.name_json == "new name json" diff --git a/fund_store/tests/test_db_routes.py b/fund_store/tests/test_db_routes.py new file mode 100644 index 000000000..4b2ef6775 --- /dev/null +++ b/fund_store/tests/test_db_routes.py @@ -0,0 +1,339 @@ +from copy import deepcopy +from datetime import datetime +from unittest.mock import patch + +from api.routes import is_valid_uuid +from fsd_test_utils.test_config.useful_config import UsefulConfig + +from db.models.event import EventType + + +def test_valid_uuid(): + uuid = "a357e264-7ef1-4f9a-be1b-6228f80c65ea" + assert is_valid_uuid(uuid) is True + uuid = "A357E264-7EF1-4F9A-BE1B-6228F80C65EA" + assert is_valid_uuid(uuid) is True + # Test Wrong UUID characters + uuid = "A357E264-7EF1-4F9A-BE1B-6228FBBBBBBBB" + assert is_valid_uuid(uuid) is False + + +def test_invalid_random(): + uuid = "abc123" + assert is_valid_uuid(uuid) is False + + +def test_invalid_None_uuid(): + uuid = None + assert is_valid_uuid(uuid) is False + uuid = "" + assert is_valid_uuid(uuid) is False + + +def test_get_fund_by_id(flask_test_client, mock_get_fund_round, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + response = flask_test_client.get("/funds/123") + assert response.status_code == 200 + result = response.json() + assert result["name"] == "Fund Name 1" + assert result["funding_type"] == "COMPETITIVE" + + +def test_get_fund_by_invalid_id(flask_test_client, mocker): + mocker.patch("api.routes.get_fund_by_id", return_value=None) + response = flask_test_client.get("/funds/None") + assert response.status_code == 404 + + +def test_get_fund_by_short_name(flask_test_client, mock_get_fund_round, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + response = flask_test_client.get("/funds/ABC?use_short_name=True") + assert response.status_code == 200 + result = response.json() + assert result["name"] == "Fund Name 1" + + +def test_get_round_by_short_name(flask_test_client, mock_get_fund_round, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + response = flask_test_client.get("/funds/FND1/rounds/RND1?use_short_name=True") + assert response.status_code == 200 + result = response.json() + assert result["title"] == "Round 1" + + +def test_get_eoi_decision_schema(flask_test_client, mock_get_fund_round, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + response = flask_test_client.get("/funds/FND1/rounds/RND1/eoi_decision_schema?use_short_name=True") + assert response.status_code == 200 + result = response.json() + assert result == {} + + +def test_get_round_by_id(flask_test_client, mock_get_fund_round, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + response = flask_test_client.get("/funds/FND1/rounds/RND1") + assert response.status_code == 200 + result = response.json() + assert result["title"] == "Round 1" + assert "eoi_decision_schema" not in result + + +def test_get_round_by_bad_id(flask_test_client, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + mocker.patch("api.routes.get_round_by_id", return_value=None) + response = flask_test_client.get("/funds/FND1/rounds/RND1") + assert response.status_code == 404 + + +def test_get_eoi_decision_schema_bad_id(flask_test_client, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + mocker.patch("api.routes.get_round_by_id", return_value=None) + response = flask_test_client.get("/funds/xxxxx/rounds/xxxxx/eoi_decision_schema") + assert response.status_code == 404 + + +def test_get_all_funds(flask_test_client, mock_get_fund_round): + response = flask_test_client.get("/funds") + assert response.status_code == 200 + result = response.json() + assert result[0]["name"] == "Fund Name 1" + + +def test_get_all_funds_no_data(flask_test_client, mocker): + mocker.patch("api.routes.get_all_funds", return_value=[]) + response = flask_test_client.get("/funds") + assert response.status_code == 200 + + +def test_get_app_sections_for_round(flask_test_client, mock_get_sections, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + response = flask_test_client.get( + f"/funds/{UsefulConfig.COF_FUND_ID}/rounds/{UsefulConfig.COF_ROUND_2_ID}/sections/application" + ) + assert response.status_code == 200 + result = response.json() + assert result[0]["title"] == "Top" + + +def test_get_assess_sections_for_round(flask_test_client, mock_get_sections, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + response = flask_test_client.get( + f"/funds/{UsefulConfig.COF_FUND_ID}/rounds/{UsefulConfig.COF_ROUND_2_ID}/sections/assessment" + ) + assert response.status_code == 200 + result = response.json() + assert result[0]["title"] == "Top" + + +def test_get_events_for_round(flask_test_client, mocker): + mock_events = [ + { + "id": "1", + "round_id": "9", + "type": EventType.APPLICATION_DEADLINE_REMINDER, + "activation_date": datetime(2000, 10, 1), + "processed": None, + }, + { + "id": "2", + "round_id": "9", + "type": EventType.APPLICATION_DEADLINE_REMINDER, + "activation_date": datetime(2001, 7, 8), + "processed": datetime(2001, 8, 8), + }, + ] + mocker.patch("api.routes.is_valid_uuid", return_value=True) + expected_response = deepcopy(mock_events) + for response in expected_response: + response["activation_date"] = response["activation_date"].isoformat() + response["processed"] = response["processed"].isoformat() if response["processed"] else None + response["type"] = response["type"].value + with patch("api.routes.get_events_from_db", return_value=mock_events) as mock_get_events_for_round_from_db: + response = flask_test_client.get("/funds/some_fund_id/rounds/some_round_id/events?only_unprocessed=true") + + assert response.status_code == 200 + assert response.json() == expected_response + mock_get_events_for_round_from_db.assert_called_once_with(round_id="some_round_id", only_unprocessed=True) + + +def test_get_events_for_round_not_found(flask_test_client, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + with patch("api.routes.get_events_from_db", return_value=None) as mock_get_events_for_round_from_db: + response = flask_test_client.get("/funds/some_fund_id/rounds/some_round_id/events") + + assert response.status_code == 404 + mock_get_events_for_round_from_db.assert_called_once_with(round_id="some_round_id", only_unprocessed=False) + + +def test_get_event(flask_test_client, mocker): + mock_event = { + "id": "1", + "round_id": "9", + "type": EventType.APPLICATION_DEADLINE_REMINDER, + "activation_date": datetime(2000, 10, 1), + "processed": None, + } + expected_response = deepcopy(mock_event) + expected_response["activation_date"] = expected_response["activation_date"].isoformat() + expected_response["processed"] = ( + expected_response["processed"].isoformat() if expected_response["processed"] else None + ) + expected_response["type"] = expected_response["type"].value + mocker.patch("api.routes.is_valid_uuid", return_value=True) + with patch("api.routes.get_event_from_db", return_value=mock_event) as mock_get_event_from_db: + response = flask_test_client.get("/funds/some_fund_id/rounds/some_round_id/event/123") + + assert response.status_code == 200 + assert response.json() == expected_response + mock_get_event_from_db.assert_called_once_with(round_id="some_round_id", event_id="123") + + +def test_get_event_not_found(flask_test_client, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + with patch("api.routes.get_event_from_db", return_value=None) as mock_get_events_for_round_from_db: + response = flask_test_client.get("/funds/some_fund_id/rounds/some_round_id/event/123") + + assert response.status_code == 404 + mock_get_events_for_round_from_db.assert_called_once_with(round_id="some_round_id", event_id="123") + + +def test_set_event_to_processed(flask_test_client, mocker): + mock_event = { + "id": "1", + "round_id": "9", + "type": EventType.APPLICATION_DEADLINE_REMINDER, + "activation_date": datetime(2000, 10, 1), + "processed": datetime(2000, 11, 1), + } + expected_response = deepcopy(mock_event) + expected_response["activation_date"] = expected_response["activation_date"].isoformat() + expected_response["type"] = expected_response["type"].value + expected_response["processed"] = expected_response["processed"].isoformat() + mocker.patch("api.routes.is_valid_uuid", return_value=True) + with patch( + "api.routes.set_event_to_processed_in_db", return_value=mock_event + ) as mock_set_round_event_to_processed_in_db: + response = flask_test_client.put("/funds/some_fund_id/rounds/some_round_id/event/123?processed=true") + + assert response.status_code == 200 + assert response.json() == expected_response + mock_set_round_event_to_processed_in_db.assert_called_once_with(event_id="123", processed=True) + + with patch( + "api.routes.set_event_to_processed_in_db", return_value=mock_event + ) as mock_set_round_event_to_processed_in_db: + response = flask_test_client.put("/event/123?processed=true") + + assert response.status_code == 200 + assert response.json() == expected_response + mock_set_round_event_to_processed_in_db.assert_called_once_with(event_id="123", processed=True) + + +def test_get_events_by_type(flask_test_client): + mock_expected_events = [ + { + "id": "1", + "type": EventType.APPLICATION_DEADLINE_REMINDER, + "round_id": "9", + "activation_date": datetime(2000, 10, 1), + "processed": None, + }, + ] + + expected_response = deepcopy(mock_expected_events) + for response in expected_response: + response["activation_date"] = response["activation_date"].isoformat() + response["processed"] = response["processed"].isoformat() if response["processed"] else None + response["type"] = response["type"].value + with patch("api.routes.get_events_from_db", return_value=mock_expected_events) as mock_get_events_by_type_from_db: + response = flask_test_client.get("/events/APPLICATION_DEADLINE_REMINDER?only_unprocessed=true") + + assert response.status_code == 200 + assert response.json() == expected_response + mock_get_events_by_type_from_db.assert_called_once_with( + type="APPLICATION_DEADLINE_REMINDER", only_unprocessed=True + ) + + +def test_get_events_by_type_not_recognised(flask_test_client, mocker): + with patch("api.routes.get_events_from_db", return_value=None): + response = flask_test_client.get("/events/INVALID_TYPE") + + assert response.status_code == 400 + + +def test_get_events_by_type_not_found(flask_test_client, mocker): + with patch("api.routes.get_events_from_db", return_value=None) as mock_get_events_by_type_from_db: + response = flask_test_client.get("/events/APPLICATION_DEADLINE_REMINDER") + + assert response.status_code == 404 + mock_get_events_by_type_from_db.assert_called_once_with( + type="APPLICATION_DEADLINE_REMINDER", only_unprocessed=False + ) + + +def test_get_event_by_id(flask_test_client, mocker): + mock_event = { + "id": "1", + "type": EventType.APPLICATION_DEADLINE_REMINDER, + "round_id": "9", + "activation_date": datetime(2000, 10, 1), + "processed": None, + } + expected_response = deepcopy(mock_event) + expected_response["activation_date"] = expected_response["activation_date"].isoformat() + expected_response["processed"] = ( + expected_response["processed"].isoformat() if expected_response["processed"] else None + ) + expected_response["type"] = expected_response["type"].value + mocker.patch("api.routes.is_valid_uuid", return_value=True) + with patch("api.routes.get_event_from_db", return_value=mock_event) as mock_get_event_from_db: + response = flask_test_client.get("/event/1") + + assert response.status_code == 200 + assert response.json() == expected_response + mock_get_event_from_db.assert_called_once_with(event_id="1") + + +def test_get_event_by_id_not_found(flask_test_client, mocker): + mocker.patch("api.routes.is_valid_uuid", return_value=True) + with patch("api.routes.get_event_from_db", return_value=None) as mock_get_event_by_id_from_db: + response = flask_test_client.get("/event/123") + + assert response.status_code == 404 + mock_get_event_by_id_from_db.assert_called_once_with(event_id="123") + + +def test_create_event(flask_test_client, mocker): + mock_events = { + "id": "1", + "type": EventType.ACCOUNT_IMPORT, + "activation_date": datetime(2000, 10, 1), + "processed": datetime(2000, 10, 1), + "round_id": None, + } + new_event_payload = { + "type": EventType.ACCOUNT_IMPORT.value, + "activation_date": datetime(2000, 10, 1).isoformat(), + "processed": datetime(2000, 10, 1).isoformat(), + } + + expected_response = {"id": "1", "round_id": None, **new_event_payload} + mocker.patch("api.routes.create_event_in_db", return_value=mock_events) + with patch("api.routes.create_event_in_db", return_value=mock_events) as mock_create_event_in_db: + response = flask_test_client.post("/event", json=new_event_payload) + mock_create_event_in_db.assert_called_once_with(**new_event_payload, round_id=None) + + assert response.status_code == 201 + assert response.json() == expected_response + + +def test_create_event_missing_type(flask_test_client): + new_event_payload = { + "round_id": "9", + "activation_date": datetime(2000, 10, 1).isoformat(), + } + response = flask_test_client.post("/event", json=new_event_payload) + + assert response.status_code == 400 + assert response.json()["detail"] == "Post body must contain event type field" diff --git a/fund_store/tests/test_eoi_schema.py b/fund_store/tests/test_eoi_schema.py new file mode 100644 index 000000000..f510461d2 --- /dev/null +++ b/fund_store/tests/test_eoi_schema.py @@ -0,0 +1,172 @@ +import json + +import pytest +from fsd_utils import Decision, evaluate_response + +from config.fund_loader_config.cof.eoi_r1_schema import ( + COF_PLANNING_PERMISSION_CAVEAT_EN, + COF_PLANNING_PERMISSION_IF_NEEDED_CAVEAT_EN, + COF_R3_EOI_SCHEMA_EN, + COF_SECURE_MATCH_FUNDING_CAVEAT_EN, +) + + +def test_eoi_schema_throws_no_errors_with_all_forms(): + with open("tests/test_data/cof_eoi.json", "r") as f: + forms = json.loads(f.read()) + result = evaluate_response(schema=COF_R3_EOI_SCHEMA_EN, forms=forms) + + assert result + + +@pytest.mark.parametrize( + "question_key,supplied_answer,exp_decision,exp_caveats", + [ + ("non-existant-question", "anything", Decision.PASS, []), + ( + "uYiLsv", + "not-yet-incorporated", + Decision.PASS_WITH_CAVEATS, + [ + "Incorporate your organisation: You must have incorporated your" + " organisation by the time you submit a full application. If you remain" + " unincorporated, your application will be ineligible." + ], + ), + ("NcQSbU", True, Decision.FAIL, []), + ("eEaDGz", False, Decision.FAIL, []), + ("zurxox", False, Decision.FAIL, []), + ("lLQmNb", False, Decision.FAIL, []), + ("fBhSNc", False, Decision.FAIL, []), + ("eOWKoO", False, Decision.FAIL, []), + ("foQgiy", False, Decision.FAIL, []), + ( + "XuAyrs", + "Yes, a town, parish or community council", + Decision.PASS_WITH_CAVEATS, + [COF_R3_EOI_SCHEMA_EN["XuAyrs"][0]["caveat"]], + ), + ( + "XuAyrs", + "Yes, another type of public authority", + Decision.PASS_WITH_CAVEATS, + [COF_R3_EOI_SCHEMA_EN["XuAyrs"][1]["caveat"]], + ), + ( + "BykoQQ", + ["Not sure"], + Decision.PASS_WITH_CAVEATS, + [COF_R3_EOI_SCHEMA_EN["BykoQQ"][0]["caveat"]], + ), + ( + "oblxxv", + False, + Decision.PASS_WITH_CAVEATS, + [COF_R3_EOI_SCHEMA_EN["oblxxv"][0]["caveat"]], + ), + ( + "kWRuac", + "Not yet approached any funders", + Decision.PASS_WITH_CAVEATS, + [COF_SECURE_MATCH_FUNDING_CAVEAT_EN], + ), + ( + "kWRuac", + "Approached some funders but not yet secured", + Decision.PASS_WITH_CAVEATS, + [COF_SECURE_MATCH_FUNDING_CAVEAT_EN], + ), + ( + "kWRuac", + "Secured some match funding", + Decision.PASS_WITH_CAVEATS, + [COF_SECURE_MATCH_FUNDING_CAVEAT_EN], + ), + ( + "kWRuac", + "Approached all funders but not yet secured", + Decision.PASS_WITH_CAVEATS, + [COF_SECURE_MATCH_FUNDING_CAVEAT_EN], + ), + ( + "yZxdeJ", + True, + Decision.PASS_WITH_CAVEATS, + [COF_R3_EOI_SCHEMA_EN["yZxdeJ"][0]["caveat"]], + ), + ( + "UORyaF", + "Not sure", + Decision.PASS_WITH_CAVEATS, + [COF_PLANNING_PERMISSION_IF_NEEDED_CAVEAT_EN], + ), + ( + "jICagT", + "Not yet started", + Decision.PASS_WITH_CAVEATS, + [COF_PLANNING_PERMISSION_CAVEAT_EN], + ), + ( + "jICagT", + "Early stage", + Decision.PASS_WITH_CAVEATS, + [COF_PLANNING_PERMISSION_CAVEAT_EN], + ), + ("fZAMFv", "2000001", Decision.FAIL, []), + ], +) +def test_answer_and_result(question_key, supplied_answer, exp_decision, exp_caveats): + # Construct a dummy form with the supplied question and answer + forms = [ + { + "name": "COF EOI Test question", + "questions": [ + { + "question": "Test question", + "fields": [ + { + "key": question_key, + "title": "test question", + "type": "test", + "answer": supplied_answer, + } + ], + } + ], + }, + ] + # evaluate a response + result = evaluate_response(schema=COF_R3_EOI_SCHEMA_EN, forms=forms) + + assert result + assert result["decision"] == exp_decision + assert result["caveats"] == exp_caveats + + +@pytest.mark.parametrize("question_key", COF_R3_EOI_SCHEMA_EN.keys()) +def test_answers_with_non_conditioned_values(question_key): + # Construct a dummy form with the supplied question and answer + forms = [ + { + "name": "COF EOI Test question", + "questions": [ + { + "question": "Test question", + "fields": [ + { + "key": question_key, + "title": "test question", + "type": "test", + "answer": "123", + } + ], + } + ], + }, + ] + # evaluate a response + result = evaluate_response(schema=COF_R3_EOI_SCHEMA_EN, forms=forms) + + assert result + assert result["decision"] == Decision.PASS + assert result["caveats"] == [] diff --git a/fund_store/tests/test_generate_all_questions.py b/fund_store/tests/test_generate_all_questions.py new file mode 100644 index 000000000..b5ade46f0 --- /dev/null +++ b/fund_store/tests/test_generate_all_questions.py @@ -0,0 +1,393 @@ +import json +import os + +import pytest +from scripts.all_questions.generate_test_data import ( + HOW_IS_ORG_CLASSIFIED, + JOINT_BID, + START_TO_MAIN_ACTIVITIES, + generate_test_data, +) +from scripts.all_questions.metadata_utils import ( + build_components_from_page, + build_hierarchy_levels_for_page, + build_section_header, + generate_metadata, + update_wording_for_multi_input_fields, +) +from scripts.all_questions.read_forms import ( + increment_lowest_in_hierarchy, + remove_lowest_in_hierarchy, + strip_leading_numbers, +) +from scripts.generate_all_questions import find_forms_dir + +from db.models.section import Section + +TEST_METADATA_FOLDER = "./tests/test_data/all_questions/metadata/" +TEST_FORMS_FOLDER = "./tests/test_data/all_questions/forms/" + + +@pytest.mark.skip(reason="Generates test data") +def test_generate_metadata(): + """Used to save generated metadata to a file, so taht file can be used for static test data""" + filename = "organisation-information-cof-r3-w2.json" + path_to_form = os.path.join( + "/path/to/digital-form-builder/fsd_config/form_jsons/cof_r3w2/en/", + filename, + ) + with open(path_to_form, "r") as f: + form_data = json.load(f) + metadata = generate_metadata(full_form_data=form_data) + with open(os.path.join(TEST_METADATA_FOLDER, f"metadata_{filename}"), "w") as f: + json.dump(metadata, f) + + +@pytest.mark.skip(reason="Generates test data") +def test_generate_test_data(): + """Used to extract a small part of metadata for easier testing.""" + output_folder = "/some/temp/folder" + files_to_generate = [START_TO_MAIN_ACTIVITIES, HOW_IS_ORG_CLASSIFIED, JOINT_BID] + generate_test_data( + target_test_files=files_to_generate, + in_path=os.path.join(TEST_METADATA_FOLDER, "some_file_name.json"), + out_folder=output_folder, + ) + + +def test_generate_index_org_info_cof_r3w2(): + with open(os.path.join(TEST_METADATA_FOLDER, "metadata_org_info_cof_r3w2.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"], start_page=True) + + assert len(results) == 18 + org_details_level = results["/organisation-names"] + assert results["/alternative-names-of-your-organisation"] == org_details_level + 1 + assert results["/purpose-and-activities"] == org_details_level + assert results["/previous-projects-similar-to-this-one"] == org_details_level + 1 + + assert results["/how-your-organisation-is-classified"] == org_details_level + assert results["/how-your-organisation-is-classified-other"] == org_details_level + 1 + # TODO why does this fail assert results["/registration-details"] == org_details_level + assert results["/trading-subsidiaries"] == org_details_level + assert results["/parent-organisation-details"] == org_details_level + 1 + + assert results["/organisation-address"] == org_details_level + assert results["/correspondence-address"] == org_details_level + 1 + assert results["/joint-applications"] == org_details_level + assert results["/partner-organisation-details"] == org_details_level + 1 + + +def test_generate_index_applicant_ns(): + with open(os.path.join(TEST_METADATA_FOLDER, "metadata_applicant_ns.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"]) + + assert len(results) == 4 + start_level = results["/13-applicant-information"] + assert results["/lead-contact-details"] == start_level + assert results["/authorised-signatory-details"] == start_level + 1 + assert results["/summary"] == start_level + + +def test_generate_index_risk_cyp(): + with open(os.path.join(TEST_METADATA_FOLDER, "metadata_risk_cyp.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"]) + + assert len(results) == 5 + start_level = results["/intro-risk-and-deliverability"] + for _, value in results.items(): + assert value == start_level + + +def test_generate_index_name_app_cyp(): + with open(os.path.join(TEST_METADATA_FOLDER, "metadata_name_app_cyp.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"]) + + assert len(results) == 2 + start_level = results["/name-your-application"] + assert results["/summary"] == start_level + + +def test_generate_index_branch_out_multi_pages_back_to_parent_sibling(): + with open(os.path.join(TEST_METADATA_FOLDER, "joint_bid_out_and_back.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"]) + + assert len(results) == 5 + start_level = results["/joint-bid"] + assert results["/partner-organisation-details"] == start_level + 1 + assert results["/work-with-partner-organisations"] == start_level + 1 + assert results["/agreement-exists"] == start_level + 1 + assert results["/website-and-social-media"] == start_level + + +def test_generate_index_branch_out_all_back_to_new(): + with open(os.path.join(TEST_METADATA_FOLDER, "how_is_org_classified.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"]) + + assert len(results) == 5 + start_level = results["/how-is-your-organisation-classified"] + assert results["/how-is-your-organisation-classified-other"] == start_level + 1 + assert results["/charity-number"] == start_level + 1 + assert results["/company-registration-number"] == start_level + 1 + assert results["/organisation-address"] == start_level + + +def test_generate_index_simple_branch(): + with open(os.path.join(TEST_METADATA_FOLDER, "start_to_main_activites.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"]) + + assert len(results) == 4 + org_details_level = results["/organisation-details"] + assert results["/alternative-organisation-name"] == org_details_level + 1 + assert results["/tell-us-about-your-organisations-main-activities"] == org_details_level + + +def test_generate_index_about_your_org_cyp(): + with open(os.path.join(TEST_METADATA_FOLDER, "metadata_about_your_org_cyp.json"), "r") as f: + form_data = json.load(f) + + results = {} + + first_page = next(p for p in form_data["all_pages"] if p["path"] == form_data["start_page"]) + build_hierarchy_levels_for_page(first_page, results, 1, form_data["all_pages"], start_page=True) + + assert len(results) == 16 + org_details_level = results["/organisation-details"] + assert results["/alternative-organisation-name"] == org_details_level + 1 + assert results["/tell-us-about-your-organisations-main-activities"] == org_details_level + assert results["/how-is-your-organisation-classified"] == org_details_level + assert results["/how-is-your-organisation-classified-other"] == org_details_level + 1 + assert results["/organisation-address"] == org_details_level + + +@pytest.mark.parametrize( + "number,exp", + [ + ("5.1", "5"), + ("5.0.0", "5.0"), + ("5.1.2", "5.1"), + ("5", ""), + ("5.1.2.3.4.6", "5.1.2.3.4"), + ], +) +def test_remove_lowest_in_hierarchy(number, exp): + assert remove_lowest_in_hierarchy(number) == exp + + +@pytest.mark.parametrize( + "number,exp", + [ + ("5.1", "5.2"), + ("5.1.", "5.2"), + ("5.1.2", "5.1.3"), + ("5", "6"), + ("5.1.2.3.4.5", "5.1.2.3.4.6"), + ], +) +def test_increment_lowest_in_hierarchy(number, exp): + assert increment_lowest_in_hierarchy(number) == exp + + +@pytest.mark.parametrize( + "text,exp", + [ + ("5.1 hello", "hello"), + ("5.1. hello", "hello"), + ("55.1. hello", "hello"), + ("5.13. hello", "hello"), + ("hello", "hello"), + ], +) +def test_strip_leading_numbers(text, exp): + assert strip_leading_numbers(text) == exp + + +@pytest.mark.parametrize( + "section,lang,exp_anchor,exp_text", + [ + ( + Section(title_json={"en": "Before You Start"}), + "en", + "before-you-start", + "Before You Start", + ), + ( + Section(title_json={"en": "1. Before You Start"}), + "en", + "before-you-start", + "Before You Start", + ), + ( + Section(title_json={"en": "Before You Start", "cy": "Welsh"}), + "cy", + "welsh", + "Welsh", + ), + ( + Section(title_json={"en": "Before You Start", "cy": "10. Welsh"}), + "cy", + "welsh", + "Welsh", + ), + ], +) +def test_build_section_headers(section, lang, exp_anchor, exp_text): + res_anchor, res_text = build_section_header(section, lang) + assert res_anchor == exp_anchor + assert res_text == exp_text + + +def test_find_forms_dir_no_lang(tmp_path): + temp_json_dir = os.path.join(tmp_path, "form_jsons") + os.mkdir(temp_json_dir) + round_dir = os.path.join(temp_json_dir, "f1_r1") + os.mkdir(round_dir) + result = find_forms_dir(temp_json_dir, "f1", "r1", "en") + assert result == round_dir + + +def test_find_forms_dir_with_lang(tmp_path): + temp_json_dir = os.path.join(tmp_path, "form_jsons") + os.mkdir(temp_json_dir) + round_dir = os.path.join(temp_json_dir, "f1_r1") + os.mkdir(round_dir) + round_dir = os.path.join(round_dir, "en") + os.mkdir(round_dir) + result = find_forms_dir(temp_json_dir, "f1", "r1", "en") + assert result == round_dir + + +def test_generate_component_display_name_your_app(): + with open( + os.path.join(TEST_FORMS_FOLDER, "name-your-application.json"), + "r", + ) as f: + form_json = json.load(f) + page_json = next(p for p in form_json["pages"] if p["path"] == "/11-name-your-application") + components = build_components_from_page(page_json, include_html_components=True) + assert len(components) == 1 + assert components[0]["title"] == "Name your application" + + +def test_build_components_empty_text_and_title(): + with open( + os.path.join(TEST_FORMS_FOLDER, "about-your-organisation-cyp.json"), + "r", + ) as f: + form_json = json.load(f) + + # Test intro has no text + page_json = next(p for p in form_json["pages"] if p["path"] == "/intro-about-your-organisation") + components = build_components_from_page(page_json, include_html_components=False) + assert len(components) == 0 + + +def test_build_components_include_options_from_radios_and_branching_text(): + with open( + os.path.join(TEST_FORMS_FOLDER, "about-your-organisation-cyp.json"), + "r", + ) as f: + form_json = json.load(f) + + # Test if for all options in how classified + page_json = next(p for p in form_json["pages"] if p["path"] == "/how-is-your-organisation-classified") + components = build_components_from_page( + page_json, + include_html_components=False, + form_lists=form_json["lists"], + form_conditions=form_json["conditions"], + index_of_printed_headers={ + "/how-is-your-organisation-classified-other": {"heading_number": "1"}, + "/charity-number": {"heading_number": "2"}, + "/company-registration-number": {"heading_number": "3"}, + }, + ) + + assert len(components) == 1 + assert components[0]["hide_title"] is True + assert len(components[0]["text"]) == 4 + assert components[0]["text"][0] == "Select one option" + assert isinstance(components[0]["text"][1], list) + assert len(components[0]["text"][1]) == 6 + assert components[0]["text"][2] == "If 'Other', go to 1" + + +def test_build_components_bullets_in_hint(): + with open( + os.path.join(TEST_FORMS_FOLDER, "your-skills-and-experience-dpi.json"), + "r", + ) as f: + form_json = json.load(f) + + page_json = next(p for p in form_json["pages"] if p["path"] == "/similar-previous-projects") + components = build_components_from_page(page_json, include_html_components=False) + assert len(components) == 1 + assert len(components[0]["text"]) == 3 + assert components[0]["text"][2] == "(Max 250 words)" + assert len(components[0]["text"][1]) == 3 + + +def test_build_components_multi_input(): + with open(os.path.join(TEST_FORMS_FOLDER, "risk-and-deliverability-cyp.json"), "r") as f: + form_json = json.load(f) + page_json = next(p for p in form_json["pages"] if p["path"] == "/risks-to-the-project") + components = build_components_from_page( + page_json, + include_html_components=True, + form_lists=form_json["lists"], + ) + + # TODO - print header with bullet lists, remove 'multiinput needed' text + assert len(components) == 2 + assert len(components[1]["text"]) == 6 + + +@pytest.mark.parametrize( + "input_text,exp_result_length", + [ + (["You can add more stuff on the next step"], 0), + (["You can add more stuff on the next step", "something else"], 1), + (["You can add more stuff on the next step", ["a list"], "something else"], 2), + (["asdfasdfasdf"], 1), + (["You can add more stuff on the next step. And do something else"], 0), + (["You can add more on the next step"], 1), + (["You can add DIFFERENT the next step"], 1), + ], +) +def test_update_wording_for_multi_input_fields(input_text, exp_result_length): + result = update_wording_for_multi_input_fields(input_text) + assert len(result) == exp_result_length diff --git a/fund_store/tests/test_healthcheck.py b/fund_store/tests/test_healthcheck.py new file mode 100644 index 000000000..74cb67f6a --- /dev/null +++ b/fund_store/tests/test_healthcheck.py @@ -0,0 +1,17 @@ +""" +A file containing all tests related to the fund endpoint. +""" + +from flask import Flask + + +def test_healthchecks_endpoint(client: Flask): + response = client.get("/healthcheck") + + expected_dict = { + "checks": [{"check_flask_running": "OK"}, {"check_db": "OK"}], + "version": "123123", + } + + assert 200 == response.status_code, "Unexpected status code" + assert expected_dict == response.json, "Unexpected json body" diff --git a/fund_store/tests/test_routes.py b/fund_store/tests/test_routes.py new file mode 100644 index 000000000..3a6cb24be --- /dev/null +++ b/fund_store/tests/test_routes.py @@ -0,0 +1,380 @@ +import pytest +from api.routes import filter_fund_by_lang, filter_round_by_lang + + +@pytest.mark.parametrize( + "fund_data, lang_key, expected", + [ + ( + { + "name_json": {"en": "English Name", "fr": "French Name"}, + "title_json": {"en": "English Title", "fr": "French Title"}, + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + }, + "en", + { + "description": "English Description", + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + "name": "English Name", + "name_json": {"en": "English Name", "fr": "French Name"}, + "title": "English Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + }, + ), + ( + { + "name_json": {"en": "English Name", "fr": "French Name"}, + "title_json": {"en": "English Title", "fr": "French Title"}, + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + }, + "fr", + { + "description": "French Description", + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + "name": "French Name", + "name_json": {"en": "English Name", "fr": "French Name"}, + "title": "French Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + }, + ), + ( + [ + { + "name_json": {"en": "English Name", "fr": "French Name"}, + "title_json": {"en": "English Title", "fr": "French Title"}, + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + }, + { + "name_json": { + "en": "Another English Name", + "fr": "Another French Name", + }, + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + "description_json": { + "en": "Another English Description", + "fr": "Another French Description", + }, + }, + ], + "en", + [ + { + "description": "English Description", + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + "name": "English Name", + "name_json": {"en": "English Name", "fr": "French Name"}, + "title": "English Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + }, + { + "description": "Another English Description", + "description_json": { + "en": "Another English Description", + "fr": "Another French Description", + }, + "name": "Another English Name", + "name_json": { + "en": "Another English Name", + "fr": "Another French Name", + }, + "title": "Another English Title", + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + }, + ], + ), + ( + [ + { + "name_json": {"en": "English Name", "fr": "French Name"}, + "title_json": {"en": "English Title", "fr": "French Title"}, + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + }, + { + "name_json": { + "en": "Another English Name", + "fr": "Another French Name", + }, + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + "description_json": { + "en": "Another English Description", + "fr": "Another French Description", + }, + }, + ], + "fr", + [ + { + "description": "French Description", + "description_json": { + "en": "English Description", + "fr": "French Description", + }, + "name": "French Name", + "name_json": {"en": "English Name", "fr": "French Name"}, + "title": "French Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + }, + { + "description": "Another French Description", + "description_json": { + "en": "Another English Description", + "fr": "Another French Description", + }, + "name": "Another French Name", + "name_json": { + "en": "Another English Name", + "fr": "Another French Name", + }, + "title": "Another French Title", + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + }, + ], + ), + ("Not a dictionary or a list", "en", "Not a dictionary or a list"), + ], +) +def test_filter_fund_by_lang(fund_data, lang_key, expected): + assert filter_fund_by_lang(fund_data, lang_key) == expected + + +@pytest.mark.parametrize( + "round_data, lang_key, expected", + [ + ( + { + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + "en", + { + "title": "English Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions": "English Instructions", + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance": "English Application Guidance", + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner": "English banner", + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + ), + ( + { + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + "fr", + { + "title": "French Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions": "French Instructions", + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance": "French Application Guidance", + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner": "French banner", + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + ), + ( + [ + { + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + { + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + "instructions_json": {"en": "Another English Instructions", "fr": "Another French Instructions"}, + "application_guidance_json": { + "en": "Another English Application Guidance", + "fr": "Another French Application Guidance", + }, + "contact_us_banner_json": { + "en": "Another English banner", + "fr": "Another French banner", + }, + }, + ], + "en", + [ + { + "title": "English Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions": "English Instructions", + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance": "English Application Guidance", + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner": "English banner", + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + { + "title": "Another English Title", + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + "instructions": "Another English Instructions", + "instructions_json": {"en": "Another English Instructions", "fr": "Another French Instructions"}, + "application_guidance": "Another English Application Guidance", + "application_guidance_json": { + "en": "Another English Application Guidance", + "fr": "Another French Application Guidance", + }, + "contact_us_banner": "Another English banner", + "contact_us_banner_json": { + "en": "Another English banner", + "fr": "Another French banner", + }, + }, + ], + ), + ( + [ + { + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + { + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + "instructions_json": {"en": "Another English Instructions", "fr": "Another French Instructions"}, + "application_guidance_json": { + "en": "Another English Application Guidance", + "fr": "Another French Application Guidance", + }, + "contact_us_banner_json": { + "en": "Another English banner", + "fr": "Another French banner", + }, + }, + ], + "fr", + [ + { + "title": "French Title", + "title_json": {"en": "English Title", "fr": "French Title"}, + "instructions": "French Instructions", + "instructions_json": {"en": "English Instructions", "fr": "French Instructions"}, + "application_guidance": "French Application Guidance", + "application_guidance_json": { + "en": "English Application Guidance", + "fr": "French Application Guidance", + }, + "contact_us_banner": "French banner", + "contact_us_banner_json": { + "en": "English banner", + "fr": "French banner", + }, + }, + { + "title": "Another French Title", + "title_json": { + "en": "Another English Title", + "fr": "Another French Title", + }, + "instructions": "Another French Instructions", + "instructions_json": {"en": "Another English Instructions", "fr": "Another French Instructions"}, + "application_guidance": "Another French Application Guidance", + "application_guidance_json": { + "en": "Another English Application Guidance", + "fr": "Another French Application Guidance", + }, + "contact_us_banner": "Another French banner", + "contact_us_banner_json": { + "en": "Another English banner", + "fr": "Another French banner", + }, + }, + ], + ), + ("Not a dictionary or a list", "en", "Not a dictionary or a list"), + ], +) +def test_filter_round_by_lang(round_data, lang_key, expected): + assert filter_round_by_lang(round_data, lang_key) == expected diff --git a/fund_store/tests/test_schemas.py b/fund_store/tests/test_schemas.py new file mode 100644 index 000000000..8b27a2547 --- /dev/null +++ b/fund_store/tests/test_schemas.py @@ -0,0 +1,156 @@ +import pytest + +from db.models import FormName +from db.models.section import Section +from db.schemas.section import SECTION_SCHEMA_MAP, EnglishSectionSchema, WelshSectionSchema + +section = Section( + id=1, + title_json={"en": "English Title", "cy": "Welsh Title"}, + form_name=[FormName(form_name_json={"en": "English Form Name", "cy": "Welsh Form Name"})], + children=[ + Section( + id=1, + title_json={"en": "English Child Section 1", "cy": "Welsh Child Section"}, + path="1.1", + ), + Section( + id=3, + title_json={"en": "English Child Section 3", "cy": "Welsh Child Section"}, + path="1.3", + ), + Section( + id=2, + title_json={"en": "English Child Section 2", "cy": "Welsh Child Section"}, + path="1.2", + ), + ], +) + +expected_en = { + "children": [ + { + "children": [], + "fields": [], + "form_name": None, + "id": 1, + "path": "1.1", + "requires_feedback": None, + "title": "English Child Section 1", + "title_json": { + "cy": "Welsh Child Section", + "en": "English Child Section 1", + }, + "weighting": None, + }, + { + "children": [], + "fields": [], + "form_name": None, + "id": 2, + "path": "1.2", + "requires_feedback": None, + "title": "English Child Section 2", + "title_json": { + "cy": "Welsh Child Section", + "en": "English Child Section 2", + }, + "weighting": None, + }, + { + "children": [], + "fields": [], + "form_name": None, + "id": 3, + "path": "1.3", + "requires_feedback": None, + "title": "English Child Section 3", + "title_json": { + "cy": "Welsh Child Section", + "en": "English Child Section 3", + }, + "weighting": None, + }, + ], + "fields": [], + "form_name": "English Form Name", + "id": 1, + "path": None, + "requires_feedback": None, + "title": "English Title", + "title_json": {"cy": "Welsh Title", "en": "English Title"}, + "weighting": None, +} + +expected_cy = { + "children": [ + { + "children": [], + "fields": [], + "form_name": None, + "id": 1, + "path": "1.1", + "requires_feedback": None, + "title": "Welsh Child Section", + "title_json": { + "cy": "Welsh Child Section", + "en": "English Child Section 1", + }, + "weighting": None, + }, + { + "children": [], + "fields": [], + "form_name": None, + "id": 2, + "path": "1.2", + "requires_feedback": None, + "title": "Welsh Child Section", + "title_json": { + "cy": "Welsh Child Section", + "en": "English Child Section 2", + }, + "weighting": None, + }, + { + "children": [], + "fields": [], + "form_name": None, + "id": 3, + "path": "1.3", + "requires_feedback": None, + "title": "Welsh Child Section", + "title_json": { + "cy": "Welsh Child Section", + "en": "English Child Section 3", + }, + "weighting": None, + }, + ], + "fields": [], + "form_name": "Welsh Form Name", + "id": 1, + "path": None, + "requires_feedback": None, + "title": "Welsh Title", + "title_json": {"cy": "Welsh Title", "en": "English Title"}, + "weighting": None, +} + + +@pytest.mark.parametrize( + "section, lang_code, expected", + [ + (section, "en", expected_en), + (section, "cy", expected_cy), + ], +) +def test_dump(section, lang_code, expected): + schema = SECTION_SCHEMA_MAP[lang_code]() + result = schema.dump(section) + assert result == expected + + +def test_section_schema_map(): + assert SECTION_SCHEMA_MAP["en"] == EnglishSectionSchema + assert SECTION_SCHEMA_MAP["cy"] == WelshSectionSchema diff --git a/fund_store/uv.lock b/fund_store/uv.lock new file mode 100644 index 000000000..f61bbdb23 --- /dev/null +++ b/fund_store/uv.lock @@ -0,0 +1,1702 @@ +version = 1 +requires-python = "==3.10.*" +resolution-markers = [ + "platform_python_implementation == 'CPython' and sys_platform != 'cygwin' and sys_platform != 'win32'", + "platform_python_implementation != 'CPython' and platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'", + "(platform_python_implementation == 'CPython' and sys_platform == 'cygwin') or (platform_python_implementation == 'CPython' and sys_platform == 'win32')", + "(platform_python_implementation != 'CPython' and platform_python_implementation != 'PyPy' and sys_platform == 'cygwin') or (platform_python_implementation != 'CPython' and platform_python_implementation != 'PyPy' and sys_platform == 'win32')", + "platform_python_implementation == 'PyPy'", +] + +[[package]] +name = "a2wsgi" +version = "1.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/1c/07d91da0c8618ecc146a811ab01985ca95ad07221483625dc00a024ea5cb/a2wsgi-1.10.4.tar.gz", hash = "sha256:50e81ac55aa609fa2c666e42bacc25c424c8884ce6072f1a7e902114b7ee5d63", size = 18186 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/a6/73b02f52206f7bc3600a702726bd75b0cda229a23c4a7ea6189bbd9ae528/a2wsgi-1.10.4-py3-none-any.whl", hash = "sha256:f17da93bf5952e0b0938c87f261c52b7305ddfab1ff3c70dd10b4b76db3851d3", size = 16812 }, +] + +[[package]] +name = "airium" +version = "0.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/98/f843cd8969409e913b0535ae15771f86d35aed87484372de6fa4e48b283f/airium-0.2.6.tar.gz", hash = "sha256:ccab36b798b6cce3d0c5074e52ce8059f6e82991caae4985f42cadfad80b1de4", size = 19496 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/bc/d174ebf44e4cf8cd967bca5e181e254bd2f95afbb5367471382713736bf5/airium-0.2.6-py3-none-any.whl", hash = "sha256:50af5cf491e084f27909e29a93550b4170e587cde01334d58c6249644ee8c6c2", size = 13313 }, +] + +[[package]] +name = "alembic" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/24/ddce068e2ac9b5581bd58602edb2a1be1b0752e1ff2963c696ecdbe0470d/alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595", size = 1213288 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/50/9fb3a5c80df6eb6516693270621676980acd6d5a9a7efdbfa273f8d616c7/alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43", size = 233424 }, +] + +[[package]] +name = "anyio" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/e3/c4c8d473d6780ef1853d630d581f70d655b4f8d7553c6997958c283039a2/anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94", size = 163930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/a2/10639a79341f6c019dedc95bd48a4928eed9f1d1197f4c04f546fc7ae0ff/anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7", size = 86780 }, +] + +[[package]] +name = "asgiref" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 }, +] + +[[package]] +name = "asserts" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/ef/a02b2af8228be2a08ffd7e7630084e441030fbd3e6426483ddcdf905ac34/asserts-0.11.1-py2.py3-none-any.whl", hash = "sha256:dfe3a45a311c727f516e9375eac85e5f5dd1df846b60ae52f559d8534b24df5d", size = 12454 }, +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/d6/21b30a550dafea84b1b8eee21b5e23fa16d010ae006011221f33dcd8d7f8/async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", size = 8345 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/fa/e01228c2938de91d47b307831c62ab9e4001e747789d0b05baf779a6488c/async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028", size = 5721 }, +] + +[[package]] +name = "attrs" +version = "23.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/fc/f800d51204003fa8ae392c4e8278f256206e7a919b708eef054f5f4b650d/attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", size = 780820 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1", size = 60752 }, +] + +[[package]] +name = "babel" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/d2/9671b93d623300f0aef82cde40e25357f11330bdde91743891b22a555bed/babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413", size = 9390000 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/45/377f7e32a5c93d94cd56542349b34efab5ca3f9e2fd5a68c5e93169aa32d/Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb", size = 9634913 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, +] + +[[package]] +name = "black" +version = "24.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/47/c9997eb470a7f48f7aaddd3d9a828244a2e4199569e38128715c48059ac1/black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d", size = 642299 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/f6/3adc48c210527a7b651aaed43824a9b8bd04b3fb361a5227bad046e1c876/black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce", size = 1631487 }, + { url = "https://files.pythonhosted.org/packages/a2/25/70aa1bec12c841a03e333e312daa0cf2fee50ea6336ac4851c93c0e2b411/black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021", size = 1456317 }, + { url = "https://files.pythonhosted.org/packages/e0/7d/7f8df0fdbbbefc4362d3eca6b69b7a8a4249a8a88dabc00a207d31fddcd7/black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063", size = 1822765 }, + { url = "https://files.pythonhosted.org/packages/5c/21/1ee97841c469c1551133cbe47448cdba9628c7d9431f74f114f02e3b233c/black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96", size = 1409336 }, + { url = "https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c", size = 205925 }, +] + +[[package]] +name = "blinker" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/57/a6a1721eff09598fb01f3c7cda070c1b6a0f12d63c83236edf79a440abcc/blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83", size = 23161 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/2a/10164ed1f31196a2f7f3799368a821765c62851ead0e630ab52b8e14b4d0/blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01", size = 9456 }, +] + +[[package]] +name = "boto3" +version = "1.34.142" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/1c/e7108fac4b3fd5bd2c2eb48abc2b07922b15f48f64445d86af940377cf2f/boto3-1.34.142.tar.gz", hash = "sha256:72daee953cfa0631c584c9e3aef594079e1fe6a2f64c81ff791dab9a7b25c013", size = 108696 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/4f/68ea33322927e8bf5c5b854cbec883d97f172b58f3f41094fb64f8031ea7/boto3-1.34.142-py3-none-any.whl", hash = "sha256:cae11cb54f79795e44248a9e53ec5c7328519019df1ba54bc01413f51c548626", size = 139174 }, +] + +[[package]] +name = "botocore" +version = "1.34.142" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/ad/b97d3de3ebbd953ff32bbcdbb5ceb2c8413db3d89b53bceef590f376c8a9/botocore-1.34.142.tar.gz", hash = "sha256:2eeb8e6be729c1f8ded723970ed6c6ac29cc3014d86a99e73428fa8bdca81f63", size = 12584267 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/b8/db573582b0f80f2ab7b37ade176a769bf93e217827f8424c230328db2106/botocore-1.34.142-py3-none-any.whl", hash = "sha256:9d8095bab0b93b9064e856730a7ffbbb4f897353d3170bec9ddccc5f4a3753bc", size = 12372481 }, +] + +[[package]] +name = "bs4" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189 }, +] + +[[package]] +name = "certifi" +version = "2024.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/b3/e02f4f397c81077ffc52a538e0aec464016f1860c472ed33bd2a1d220cc5/certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", size = 165550 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/11/1e78951465b4a225519b8c3ad29769c49e0d8d157a070f681d5b6d64737f/certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56", size = 164433 }, +] + +[[package]] +name = "cffi" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/ce/95b0bae7968c65473e1298efb042e10cafc7bafc14d9e4f154008241c91d/cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", size = 512873 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/aa/1c43e48a6f361d1529f9e4602d6992659a0107b5f21cae567e2eddcf8d66/cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", size = 182457 }, + { url = "https://files.pythonhosted.org/packages/c4/01/f5116266fe80c04d4d1cc96c3d355606943f9fb604a810e0b02228a0ce19/cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", size = 176792 }, + { url = "https://files.pythonhosted.org/packages/57/3a/c263cf4d5b02880274866968fa2bf196a02c4486248bc164732319b4a4c0/cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", size = 423848 }, + { url = "https://files.pythonhosted.org/packages/f0/31/a6503a5c4874fb4d4c2053f73f09a957cb427b6943fab5a43b8e156df397/cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", size = 446005 }, + { url = "https://files.pythonhosted.org/packages/22/05/43cfda378da7bb0aa19b3cf34fe54f8867b0d581294216339d87deefd69c/cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", size = 452639 }, + { url = "https://files.pythonhosted.org/packages/54/49/b8875986beef2e74fc668b95f2df010e354f78e009d33d95b375912810c3/cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", size = 434140 }, + { url = "https://files.pythonhosted.org/packages/c9/7c/43d81bdd5a915923c3bad5bb4bff401ea00ccc8e28433fb6083d2e3bf58e/cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", size = 443865 }, + { url = "https://files.pythonhosted.org/packages/eb/de/4f644fc78a1144a897e1f908abfb2058f7be05a8e8e4fe90b7f41e9de36b/cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", size = 436867 }, + { url = "https://files.pythonhosted.org/packages/ee/68/74a2b9f9432b70d97d1184cdabf32d7803124c228adef9481d280864a4a7/cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", size = 465830 }, + { url = "https://files.pythonhosted.org/packages/20/18/76e26bcfa6a7a62f880791122261575b3048ac57dd72f300ba0827629ab8/cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", size = 172955 }, + { url = "https://files.pythonhosted.org/packages/be/3e/0b197d1bfbf386a90786b251dbf2634a15f2ea3d4e4070e99c7d1c7689cf/cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", size = 181616 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 }, + { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 }, + { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 }, + { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 }, + { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 }, + { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 }, + { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 }, + { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 }, + { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 }, + { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 }, + { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 }, + { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 }, + { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 }, + { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 }, + { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "colored" +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/98/4d4546307039955eec131cf9538732fb7a28d2db2090cd1d4e4a135829e1/colored-2.2.4.tar.gz", hash = "sha256:595e1dd7f3b472ea5f12af21d2fec8a2ea2cf8f9d93e67180197330b26df9b61", size = 13202 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/d1/548f697f88872321525e294f8863efbdd1c313964b7f94e49ab0dc4f2895/colored-2.2.4-py3-none-any.whl", hash = "sha256:a7069673bd90a35f46cb748d012c17284a0668d2f1c06bc7a51822a2d5ad2112", size = 16109 }, +] + +[[package]] +name = "commonmark" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/48/a60f593447e8f0894ebb7f6e6c1f25dafc5e89c5879fdc9360ae93ff83f0/commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", size = 95764 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/92/dfd892312d822f36c55366118b95d914e5f16de11044a27cf10a7d71bbbf/commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9", size = 51068 }, +] + +[[package]] +name = "connexion" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "httpx" }, + { name = "inflection" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/74/a6d4aa579c8afc9acb0f9cefc1ae2f1da1564cd5b244bdd434407081535f/connexion-3.1.0.tar.gz", hash = "sha256:66a44580991f53955b6e409a84fa9fa65c7ca4db52dc217b49cd35c201066083", size = 88189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/55/e943e94b123b72e181c26a20205639f1f680578dd78deef903b1349994ae/connexion-3.1.0-py3-none-any.whl", hash = "sha256:e92b6d0412eb54b3b69f2516b93d982a06b0e23f6d5c1ab94257c55d365f63ce", size = 113099 }, +] + +[package.optional-dependencies] +flask = [ + { name = "a2wsgi" }, + { name = "flask", extra = ["async"] }, +] +swagger-ui = [ + { name = "swagger-ui-bundle" }, +] +uvicorn = [ + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "cryptography" +version = "42.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/a7/1498799a2ea06148463a9a2c10ab2f6a921a74fb19e231b27dc412a748e2/cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", size = 671250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/8b/1b929ba8139430e09e140e6939c2b29c18df1f2fc2149e41bdbdcdaf5d1f/cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", size = 5899961 }, + { url = "https://files.pythonhosted.org/packages/fa/5d/31d833daa800e4fab33209843095df7adb4a78ea536929145534cbc15026/cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", size = 3114353 }, + { url = "https://files.pythonhosted.org/packages/5d/32/f6326c70a9f0f258a201d3b2632bca586ea24d214cec3cf36e374040e273/cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", size = 3647773 }, + { url = "https://files.pythonhosted.org/packages/35/66/2d87e9ca95c82c7ee5f2c09716fc4c4242c1ae6647b9bd27e55e920e9f10/cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", size = 3839763 }, + { url = "https://files.pythonhosted.org/packages/c2/de/8083fa2e68d403553a01a9323f4f8b9d7ffed09928ba25635c29fb28c1e7/cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", size = 3632661 }, + { url = "https://files.pythonhosted.org/packages/07/40/d6f6819c62e808ea74639c3c640f7edd636b86cce62cb14943996a15df92/cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", size = 3851536 }, + { url = "https://files.pythonhosted.org/packages/5c/46/de71d48abf2b6d3c808f4fbb0f4dc44a4e72786be23df0541aa2a3f6fd7e/cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", size = 3754209 }, + { url = "https://files.pythonhosted.org/packages/25/c9/86f04e150c5d5d5e4a731a2c1e0e43da84d901f388e3fea3d5de98d689a7/cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", size = 3923551 }, + { url = "https://files.pythonhosted.org/packages/53/c2/903014dafb7271fb148887d4355b2e90319cad6e810663be622b0c933fc9/cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", size = 3739265 }, + { url = "https://files.pythonhosted.org/packages/95/26/82d704d988a193cbdc69ac3b41c687c36eaed1642cce52530ad810c35645/cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", size = 3937371 }, + { url = "https://files.pythonhosted.org/packages/cf/71/4e0d05c9acd638a225f57fb6162aa3d03613c11b76893c23ea4675bb28c5/cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", size = 2438849 }, + { url = "https://files.pythonhosted.org/packages/06/0f/78da3cad74f2ba6c45321dc90394d70420ea846730dc042ef527f5a224b5/cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", size = 2889090 }, + { url = "https://files.pythonhosted.org/packages/60/12/f064af29190cdb1d38fe07f3db6126091639e1dece7ec77c4ff037d49193/cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", size = 5901232 }, + { url = "https://files.pythonhosted.org/packages/43/c2/4a3eef67e009a522711ebd8ac89424c3a7fe591ece7035d964419ad52a1d/cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", size = 3648711 }, + { url = "https://files.pythonhosted.org/packages/49/1c/9f6d13cc8041c05eebff1154e4e71bedd1db8e174fff999054435994187a/cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", size = 3841968 }, + { url = "https://files.pythonhosted.org/packages/5f/f9/c3d4f19b82bdb25a3d857fe96e7e571c981810e47e3f299cc13ac429066a/cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", size = 3633032 }, + { url = "https://files.pythonhosted.org/packages/fa/e2/b7e6e8c261536c489d9cf908769880d94bd5d9a187e166b0dc838d2e6a56/cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", size = 3852478 }, + { url = "https://files.pythonhosted.org/packages/a2/68/e16751f6b859bc120f53fddbf3ebada5c34f0e9689d8af32884d8b2e4b4c/cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e", size = 3754102 }, + { url = "https://files.pythonhosted.org/packages/0f/38/85c74d0ac4c540780e072b1e6f148ecb718418c1062edcb20d22f3ec5bbb/cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", size = 3925042 }, + { url = "https://files.pythonhosted.org/packages/89/f4/a8b982e88eb5350407ebdbf4717b55043271d878705329e107f4783555f2/cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", size = 3738833 }, + { url = "https://files.pythonhosted.org/packages/fd/2b/be327b580645927bb1a1f32d5a175b897a9b956bc085b095e15c40bac9ed/cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", size = 3938751 }, + { url = "https://files.pythonhosted.org/packages/3c/d5/c6a78ffccdbe4516711ebaa9ed2c7eb6ac5dfa3dc920f2c7e920af2418b0/cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", size = 2439281 }, + { url = "https://files.pythonhosted.org/packages/a2/7b/b0d330852dd5953daee6b15f742f15d9f18e9c0154eb4cfcc8718f0436da/cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", size = 2886038 }, + { url = "https://files.pythonhosted.org/packages/a3/fe/1e21699f0a7904e8a30d4fc6db262958f1edf5e505a02e7d97a5b419e482/cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", size = 3014449 }, + { url = "https://files.pythonhosted.org/packages/d5/f3/61b398b5ec61f4b6ffbf746227df7ebb421696458d9625d634043f236a13/cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", size = 3558533 }, + { url = "https://files.pythonhosted.org/packages/c1/e2/60b05e720766e185ef097d07068bd878a51d613ef91e4c241750f9c9192b/cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", size = 3759330 }, + { url = "https://files.pythonhosted.org/packages/10/38/2c8dae407d301eaf942e377a5b2b30485cfa0df03c6c2dcc2ac044054ed9/cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", size = 2801764 }, +] + +[[package]] +name = "debugpy" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/c7/a18e15ed2e53f86de2e1c4162a54ddf1c4f4cee5ca40270c14725ccdd8ff/debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42", size = 4619053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/00/629fd2ba18483496482fd4b640c59b904238472c004036f331729753c63c/debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741", size = 1714331 }, + { url = "https://files.pythonhosted.org/packages/7a/27/78d5cf9c7aba43f8341e78273ab776913d2d33beb581ec39b65e56a0db77/debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e", size = 2998681 }, + { url = "https://files.pythonhosted.org/packages/ee/28/69b62b9e21d0e8d5ad45e5c0323f921a00ccc436bf144940b084fb898d86/debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0", size = 4771701 }, + { url = "https://files.pythonhosted.org/packages/a2/81/408eecc856b931b4db262bd3402534d815e22dc7ebcfdc333cb57e69e9f9/debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd", size = 4796491 }, + { url = "https://files.pythonhosted.org/packages/57/ab/6df7e24db51e1db642a5ea1759d44fb656251253995a27deb37af9b192ae/debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242", size = 4832569 }, +] + +[[package]] +name = "deepdiff" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ordered-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/10/6f4b0bd0627d542f63a24f38e29d77095dc63d5f45bc1a7b4a6ca8750fa9/deepdiff-7.0.1.tar.gz", hash = "sha256:260c16f052d4badbf60351b4f77e8390bee03a0b516246f6839bc813fb429ddf", size = 421718 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/e6/d27d37dc55dbf40cdbd665aa52844b065ac760c9a02a02265f97ea7a4256/deepdiff-7.0.1-py3-none-any.whl", hash = "sha256:447760081918216aa4fd4ca78a4b6a848b81307b2ea94c810255334b759e1dc3", size = 80825 }, +] + +[[package]] +name = "distlib" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/91/e2df406fb4efacdf46871c25cde65d3c6ee5e173b7e5a4547a47bae91920/distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64", size = 609931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", size = 468850 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/65/d66b7fbaef021b3c954b3bbb196d21d8a4b97918ea524f82cfae474215af/exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16", size = 28717 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/90/79fe92dd413a9cab314ef5c591b5aa9b9ba787ae4cadab75055b0ae00b33/exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", size = 16458 }, +] + +[[package]] +name = "filelock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7d/73d36db6955bde2ed495ce40ce02c9a2533b8c7b64fd42a38b1ee879ea18/filelock-3.15.1.tar.gz", hash = "sha256:58a2549afdf9e02e10720eaa4d4470f56386d7a6f72edd7d0596337af8ed7ad8", size = 17564 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/aa/edf5205465b70cee020b711f1f4b6179a0ae369cc13aadb8f8ec6fd7d2f5/filelock-3.15.1-py3-none-any.whl", hash = "sha256:71b3102950e91dfc1bb4209b64be4dc8854f40e5f534428d8684f953ac847fac", size = 15946 }, +] + +[[package]] +name = "flake8" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/34/64f8a43736d9862ced7dd0ea5c3ed99815b8ff4b826a4f3bfd3a1b0639b1/flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5", size = 48240 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/43/d5147aadaa52558e94e024811f2f9543b4bd7203b3a9659eeb5dff9c61b3/flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a", size = 57569 }, +] + +[[package]] +name = "flake8-pyproject" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/1d/635e86f9f3a96b7ea9e9f19b5efe17a987e765c39ca496e4a893bb999112/flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a", size = 4756 }, +] + +[[package]] +name = "flask" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/e1/d104c83026f8d35dfd2c261df7d64738341067526406b40190bc063e829a/flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842", size = 676315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3", size = 101735 }, +] + +[package.optional-dependencies] +async = [ + { name = "asgiref" }, +] + +[[package]] +name = "flask-babel" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "flask" }, + { name = "jinja2" }, + { name = "pytz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/1a/4c65e3b90bda699a637bfb7fb96818b0a9bbff7636ea91aade67f6020a31/flask_babel-4.0.0.tar.gz", hash = "sha256:dbeab4027a3f4a87678a11686496e98e1492eb793cbdd77ab50f4e9a2602a593", size = 10178 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/c2/e0ab5abe37882e118482884f2ec660cd06da644ddfbceccf5f88f546b574/flask_babel-4.0.0-py3-none-any.whl", hash = "sha256:638194cf91f8b301380f36d70e2034c77ee25b98cb5d80a1626820df9a6d4625", size = 9602 }, +] + +[[package]] +name = "flask-json" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/4a/6046e195241772f4b6add3adcd4e820004c6151afc587d8c7d0d4ffeb473/Flask-JSON-0.4.0.tar.gz", hash = "sha256:07945d66024f3b77694ce1db5d1fe83940f2aa3bcad8a608535686be67e4bc48", size = 15899 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/87/3b3ae3ca23dd90b15faa1128c473c1cf16d42e9f95efc5e4a1b77bf22260/Flask_JSON-0.4.0-py3-none-any.whl", hash = "sha256:1c1b87a657daa2179fc19f1ffc78204a716c7c5139673dc5038772db4d9f1988", size = 8714 }, +] + +[[package]] +name = "flask-migrate" +version = "4.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "flask" }, + { name = "flask-sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/e2/4008fc0d298d7ce797021b194bbe151d4d12db670691648a226d4fc8aefc/Flask-Migrate-4.0.7.tar.gz", hash = "sha256:dff7dd25113c210b069af280ea713b883f3840c1e3455274745d7355778c8622", size = 21770 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/01/587023575286236f95d2ab8a826c320375ed5ea2102bb103ed89704ffa6b/Flask_Migrate-4.0.7-py3-none-any.whl", hash = "sha256:5c532be17e7b43a223b7500d620edae33795df27c75811ddf32560f7d48ec617", size = 21127 }, +] + +[[package]] +name = "flask-redis" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/d1/6e5a087e2fd99782451312dd467bbf5f9f64d999de900047dc0854a7d175/flask-redis-0.4.0.tar.gz", hash = "sha256:e1fccc11e7ea35c2a4d68c0b9aa58226a098e45e834d615c7b6c4928b01ddd6c", size = 9906 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/9c/cead8fff1c8da2bd31a83ec476c3364812ee74f3c7c3445d070555f681d1/flask_redis-0.4.0-py2.py3-none-any.whl", hash = "sha256:8d79eef4eb1217095edab603acc52f935b983ae4b7655ee7c82c0dfd87315d17", size = 8550 }, +] + +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/53/b0a9fcc1b1297f51e68b69ed3b7c3c40d8c45be1391d77ae198712914392/flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312", size = 81899 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/6a/89963a5c6ecf166e8be29e0d1bf6806051ee8fe6c82e232842e3aeac9204/flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0", size = 25125 }, +] + +[[package]] +name = "funding-service-design-fund-store" +version = "0.1.1" +source = { virtual = "." } +dependencies = [ + { name = "airium" }, + { name = "bs4" }, + { name = "connexion", extra = ["flask", "swagger-ui", "uvicorn"] }, + { name = "flask" }, + { name = "flask-json" }, + { name = "flask-migrate" }, + { name = "flask-sqlalchemy" }, + { name = "funding-service-design-utils" }, + { name = "marshmallow-sqlalchemy" }, + { name = "openapi-spec-validator" }, + { name = "prance" }, + { name = "psycopg2-binary" }, + { name = "pytest-html" }, + { name = "pytest-mock" }, + { name = "sqlalchemy", extra = ["mypy"] }, + { name = "sqlalchemy-json" }, + { name = "sqlalchemy-utils" }, + { name = "swagger-ui-bundle" }, + { name = "uvicorn" }, +] + +[package.dependency-groups] +dev = [ + { name = "asserts" }, + { name = "black" }, + { name = "colored" }, + { name = "debugpy" }, + { name = "deepdiff" }, + { name = "flake8-pyproject" }, + { name = "invoke" }, + { name = "json2html" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-env" }, + { name = "pytest-flask" }, + { name = "pytest-mock" }, +] + +[package.metadata] +requires-dist = [ + { name = "airium", specifier = ">=0.2.6" }, + { name = "bs4", specifier = ">=0.0.2" }, + { name = "connexion", extras = ["flask", "swagger-ui", "uvicorn"], specifier = ">=3.1.0" }, + { name = "flask", specifier = "==3.0.3" }, + { name = "flask-json", specifier = "==0.4.0" }, + { name = "flask-migrate", specifier = "==4.0.7" }, + { name = "flask-sqlalchemy", specifier = "==3.1.1" }, + { name = "funding-service-design-utils", specifier = ">=5.0.8,<6.0.0" }, + { name = "marshmallow-sqlalchemy", specifier = "==1.0.0" }, + { name = "openapi-spec-validator", specifier = ">=0.7.1" }, + { name = "prance", specifier = ">=23.6.21.0" }, + { name = "psycopg2-binary", specifier = "==2.9.9" }, + { name = "pytest-html", specifier = ">=3.2.0" }, + { name = "pytest-mock", specifier = "==3.14.0" }, + { name = "sqlalchemy", extras = ["mypy"], specifier = ">=2.0.30" }, + { name = "sqlalchemy-json", specifier = "==0.7.0" }, + { name = "sqlalchemy-utils", specifier = "==0.41.2" }, + { name = "swagger-ui-bundle", specifier = "==1.1.0" }, + { name = "uvicorn", specifier = "==0.30.1" }, +] + +[package.metadata.dependency-groups] +dev = [ + { name = "asserts", specifier = "==0.11.1" }, + { name = "black", specifier = ">=24.4.2" }, + { name = "colored", specifier = ">=2.2.4" }, + { name = "debugpy", specifier = ">=1.8.1" }, + { name = "deepdiff", specifier = ">=7.0.1" }, + { name = "flake8-pyproject", specifier = ">=1.2.3" }, + { name = "invoke", specifier = ">=2.2.0" }, + { name = "json2html", specifier = "==1.3.0" }, + { name = "pre-commit", specifier = "~=4.0.0" }, + { name = "pytest", specifier = ">=8.2.2" }, + { name = "pytest-env", specifier = ">=1.1.3" }, + { name = "pytest-flask", specifier = ">=1.3.0" }, + { name = "pytest-mock", specifier = "==3.14.0" }, +] + +[[package]] +name = "funding-service-design-utils" +version = "5.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "boto3" }, + { name = "flask" }, + { name = "flask-babel" }, + { name = "flask-migrate" }, + { name = "flask-redis" }, + { name = "flask-sqlalchemy" }, + { name = "gunicorn" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-dotenv" }, + { name = "python-json-logger" }, + { name = "pytz" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, + { name = "sentry-sdk", extra = ["flask"] }, + { name = "sqlalchemy-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/54/051e40877eaf86a80763aa635c212bc26d5b6b59893f297851124e181613/funding_service_design_utils-5.0.8.tar.gz", hash = "sha256:a7c0c0acf4031f375f0cfc4974b2fa4ba10e68fcea7e5a38b50a88c407916e2a", size = 67134 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/1b/4c3a8f89a3307d8381de2fae28d6322aea11b4a580cbdb3c7f48f6633cac/funding_service_design_utils-5.0.8-py3-none-any.whl", hash = "sha256:4582eef625c13c4059648b916040cf21e39e928c5e57326f8fc461e84eb34982", size = 81075 }, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235 }, + { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168 }, + { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826 }, + { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443 }, + { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295 }, + { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544 }, + { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456 }, + { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111 }, + { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392 }, +] + +[[package]] +name = "gunicorn" +version = "22.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/88/e2f93c5738a4c1f56a458fc7a5b1676fc31dcdbb182bef6b40a141c17d66/gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63", size = 3639760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/97/6d610ae77b5633d24b69c2ff1ac3044e0e565ecbd1ec188f02c45073054c/gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9", size = 84443 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/b0/5e8b8674f8d203335a62fdfcfa0d11ebe09e23613c3391033cbba35f7926/httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61", size = 83234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/d4/e5d7e4f2174f8a4d63c8897d79eb8fe2503f7ecc03282fee1fa2719c2704/httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5", size = 77926 }, +] + +[[package]] +name = "httptools" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/d77686502fced061b3ead1c35a2d70f6b281b5f723c4eff7a2277c04e4a2/httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", size = 191228 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6a/80bce0216b63babf51cdc34814c3f0f10489e13ab89fb6bc91202736a8a2/httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f", size = 149778 }, + { url = "https://files.pythonhosted.org/packages/bd/7d/4cd75356dfe0ed0b40ca6873646bf9ff7b5138236c72338dc569dc57d509/httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563", size = 77604 }, + { url = "https://files.pythonhosted.org/packages/4e/74/6348ce41fb5c1484f35184c172efb8854a288e6090bb54e2210598268369/httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58", size = 346717 }, + { url = "https://files.pythonhosted.org/packages/65/e7/dd5ba95c84047118a363f0755ad78e639e0529be92424bb020496578aa3b/httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185", size = 341442 }, + { url = "https://files.pythonhosted.org/packages/d8/97/b37d596bc32be291477a8912bf9d1508d7e8553aa11a30cd871fd89cbae4/httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142", size = 354531 }, + { url = "https://files.pythonhosted.org/packages/99/c9/53ed7176583ec4b4364d941a08624288f2ae55b4ff58b392cdb68db1e1ed/httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658", size = 347754 }, + { url = "https://files.pythonhosted.org/packages/1e/fc/8a26c2adcd3f141e4729897633f03832b71ebea6f4c31cce67a92ded1961/httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b", size = 58165 }, +] + +[[package]] +name = "httpx" +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/3da5bdf4408b8b2800061c339f240c1802f2e82d55e50bd39c5a881f47f0/httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5", size = 126413 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/7b/ddacf6dcebb42466abd03f368782142baa82e08fc0c1f8eaa05b4bae87d5/httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5", size = 75590 }, +] + +[[package]] +name = "identify" +version = "2.5.36" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/9a/83775a4e09de8b9d774a2217bfe03038c488778e58561e6970daa39b4801/identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d", size = 99049 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/d3/d31b7fe744a3b2e6c51ea04af6575d1583deb09eb33cecfc99fa7644a725/identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa", size = 98970 }, +] + +[[package]] +name = "idna" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/ed/f86a79a07470cb07819390452f178b3bef1d375f2ec021ecfc709fc7cf07/idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", size = 189575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0", size = 66836 }, +] + +[[package]] +name = "inflection" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "invoke" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/42/127e6d792884ab860defc3f4d80a8f9812e48ace584ffc5a346de58cdc6c/invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5", size = 299835 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/66/7f8c48009c72d73bc6bbe6eb87ac838d6a526146f7dab14af671121eb379/invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820", size = 160274 }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 }, +] + +[[package]] +name = "json2html" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/d5/40b617ee19d2d79f606ed37f8a81e51158f126d2af67270c68f2b47ae0d5/json2html-1.3.0.tar.gz", hash = "sha256:8951a53662ae9cfd812685facdba693fc950ffc1c1fd1a8a2d3cf4c34600689c", size = 6977 } + +[[package]] +name = "jsonschema" +version = "4.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/f1/1c1dc0f6b3bf9e76f7526562d29c320fa7d6a2f35b37a1392cc0acd58263/jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7", size = 325490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/2f/324fab4be6fe37fb7b521546e8a557e6cf08c1c1b3d0b4839a00f589d9ef/jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802", size = 88316 }, +] + +[[package]] +name = "jsonschema-path" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/17/47bf2da4582a6d35a1254bc058258835a452698f97dade2ce9ed3dabd512/jsonschema_path-0.3.2.tar.gz", hash = "sha256:4d0dababf341e36e9b91a5fb2a3e3fd300b0150e7fe88df4e55cc8253c5a3989", size = 11597 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/5a/f405ced79c55191e460fc6d17a14845fddf09f601e39cfcab28cc1d3ff1c/jsonschema_path-0.3.2-py3-none-any.whl", hash = "sha256:271aedfefcd161a0f467bdf23e1d9183691a61eaabf4b761046a914e369336c7", size = 14813 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b9/cc0cc592e7c195fb8a650c1d5990b10175cf13b4c97465c72ec841de9e4b/jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", size = 13983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/07/44bd408781594c4d0a027666ef27fab1e441b109dc3b76b4f836f8fd04fe/jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c", size = 18482 }, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/f0/f02e2d150d581a294efded4020094a371bbab42423fe78625ac18854d89b/lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69", size = 43271 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/42/a96d9d153f6ea38b925494cb9b42cf4a9f98fd30cad3124fc22e9d04ec34/lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977", size = 27432 }, + { url = "https://files.pythonhosted.org/packages/4a/0d/b325461e43dde8d7644e9b9e9dd57f2a4af472b588c51ccbc92778e60ea4/lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3", size = 69133 }, + { url = "https://files.pythonhosted.org/packages/8b/fc/83711d743fb5aaca5747bbf225fe3b5cbe085c7f6c115856b5cce80f3224/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", size = 68272 }, + { url = "https://files.pythonhosted.org/packages/8d/b5/ea47215abd4da45791664d7bbfe2976ca0de2c37af38b5e9e6cf89e0e65e/lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895", size = 70891 }, + { url = "https://files.pythonhosted.org/packages/8b/9b/908e12e5fa265ea1579261ff80f7b2136fd2ba254bc7f4f7e3dba83fd0f2/lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83", size = 70451 }, + { url = "https://files.pythonhosted.org/packages/16/ab/d9a47f2e70767af5ee311d71109be6ef2991c66c77bfa18e66707edd9f8c/lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9", size = 25778 }, + { url = "https://files.pythonhosted.org/packages/74/d6/0104e4154d2c30227eb54491dda8a4132be046b4cb37fb4ce915a5abc0d5/lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4", size = 27551 }, + { url = "https://files.pythonhosted.org/packages/31/8b/94dc8d58704ab87b39faed6f2fc0090b9d90e2e2aa2bbec35c79f3d2a054/lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d", size = 16405 }, +] + +[[package]] +name = "mako" +version = "1.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/03/fb5ba97ff65ce64f6d35b582aacffc26b693a98053fa831ab43a437cbddb/Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc", size = 392738 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/62/70f5a0c2dd208f9f3f2f9afd103aec42ee4d9ad2401d78342f75e9b8da36/Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a", size = 78565 }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206 }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079 }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620 }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818 }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493 }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630 }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745 }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021 }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659 }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213 }, +] + +[[package]] +name = "marshmallow" +version = "3.21.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/31/0881962e77efa2d524ca80566ba1fb7cab000edaa9f4152b97a39b8d9a2d/marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662", size = 176279 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/d7/f318261e6ccbba86bdf626e07cd850981508fdaec52cfcdc4ac1030327ab/marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1", size = 49201 }, +] + +[[package]] +name = "marshmallow-sqlalchemy" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/e5/6ed1255b8b252cbc063082c85db3690ff40118c891ebda3cf633ec065322/marshmallow_sqlalchemy-1.0.0.tar.gz", hash = "sha256:20a0f2fcdd5bddc86444fa01461f17f9b6a12a8ddd4ca8c9b34fe2f2e35d00a2", size = 49747 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/32/95b3e03d41480e5e8963034ed569e94cd5febe64bc23240936b108592bbb/marshmallow_sqlalchemy-1.0.0-py3-none-any.whl", hash = "sha256:f415d57809e3555b6323356589aba91e36e4470f35953d3a10c755ac5c3307df", size = 14427 }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, +] + +[[package]] +name = "mypy" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b6/297734bb9f20ddf5e831cf4a83f422ddef5a29a33463999f0959d9cdc2df/mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131", size = 3022145 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/82/2081dbfbbf1071e1370e57f9e327adeda060113688ec0d6bf7bbf4d7a5ad/mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2", size = 10819193 }, + { url = "https://files.pythonhosted.org/packages/e8/1b/b7c9caa89955a7d9c89eac79f31550f48f2c8233b5e12fe48ef55cd2e953/mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99", size = 9970689 }, + { url = "https://files.pythonhosted.org/packages/15/ae/03d3f767f1ca5576970720ea551b43b79254d12998484d8f3e63fc07561e/mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2", size = 12728098 }, + { url = "https://files.pythonhosted.org/packages/96/ba/8f5db8bd94c18d86033d09bbe634d471c1e9d7014cc621585973183ad1d0/mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9", size = 12798838 }, + { url = "https://files.pythonhosted.org/packages/0e/ad/d476f1055deea6e63a91e065ba046a7ee494705574c4f9730de439172810/mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051", size = 9365995 }, + { url = "https://files.pythonhosted.org/packages/e9/39/0148f7ee1b7f3a86d378a23b88cb85c432f83914ceb60364efa1769c598f/mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee", size = 2580084 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "openapi-schema-validator" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-specifications" }, + { name = "rfc3339-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/b2/7d5bdf2b26b6a95ebf4fbec294acaf4306c713f3a47c2453962511110248/openapi_schema_validator-0.6.2.tar.gz", hash = "sha256:11a95c9c9017912964e3e5f2545a5b11c3814880681fcacfb73b1759bb4f2804", size = 11860 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/dc/9aefae8891454130968ff079ece851d1ae9ccf6fb7965761f47c50c04853/openapi_schema_validator-0.6.2-py3-none-any.whl", hash = "sha256:c4887c1347c669eb7cded9090f4438b710845cd0f90d1fb9e1b3303fb37339f8", size = 8750 }, +] + +[[package]] +name = "openapi-spec-validator" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "lazy-object-proxy" }, + { name = "openapi-schema-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/fe/21954ff978239dc29ebb313f5c87eeb4ec929b694b9667323086730998e2/openapi_spec_validator-0.7.1.tar.gz", hash = "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7", size = 37985 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/4d/e744fff95aaf3aeafc968d5ba7297c8cda0d1ecb8e3acd21b25adae4d835/openapi_spec_validator-0.7.1-py3-none-any.whl", hash = "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959", size = 38998 }, +] + +[[package]] +name = "ordered-set" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[package]] +name = "pathable" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/ed/e0e29300253b61dea3b7ec3a31f5d061d577c2a6fd1e35c5cfd0e6f2cd6d/pathable-0.4.3.tar.gz", hash = "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab", size = 8679 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/0a/acfb251ba01009d3053f04f4661e96abf9d485266b04a0a4deebc702d9cb/pathable-0.4.3-py3-none-any.whl", hash = "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14", size = 9587 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "prance" +version = "23.6.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, + { name = "packaging" }, + { name = "requests" }, + { name = "ruamel-yaml" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/f0/bcb5ffc8b7ab8e3d02dbef3bd945cf8fd6e12c146774f900659406b9fce1/prance-23.6.21.0.tar.gz", hash = "sha256:d8c15f8ac34019751cc4945f866d8d964d7888016d10de3592e339567177cabe", size = 2798776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/db/4fb4901ee61274d0ab97746461fc5f2637e5d73aa73f34ee28e941a699a1/prance-23.6.21.0-py3-none-any.whl", hash = "sha256:6a4276fa07ed9f22feda4331097d7503c4adc3097e46ffae97425f2c1026bd9f", size = 36279 }, +] + +[[package]] +name = "pre-commit" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/07/e720e53bfab016ebcc34241695ccc06a9e3d91ba19b40ca81317afbdc440/psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c", size = 384973 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7c/6aaf8c3cb05d86d2c3f407b95bac0c71a43f2718e38c1091972aacb5e1b2/psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202", size = 2822503 }, + { url = "https://files.pythonhosted.org/packages/72/3d/acab427845198794aafd963dd073ee35810e2c52606e8a28c12db39821fb/psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7", size = 2552645 }, + { url = "https://files.pythonhosted.org/packages/ed/be/6c787962d706e55a528ef1693dd7251de657ae60e4d9d767ed61e8e2975c/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b", size = 2850980 }, + { url = "https://files.pythonhosted.org/packages/83/50/a054076c6358753661cd1da59f4dabc03e83d51690371f3fd1edb9e2cf72/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9", size = 3080543 }, + { url = "https://files.pythonhosted.org/packages/9c/02/826dc5cdfc9515423ec912ba00cc2e4eb09f69e0339b177c9c742f2a09a2/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84", size = 3264316 }, + { url = "https://files.pythonhosted.org/packages/bc/0d/486e3fa27f39a00168abfcf14a3d8444f437f4b755cc34316da1124f293d/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e", size = 3019508 }, + { url = "https://files.pythonhosted.org/packages/41/af/bce37630c525d2b9cf93f930110fc98616d6aca308d59b833b83b3a38176/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98", size = 2355821 }, + { url = "https://files.pythonhosted.org/packages/3b/76/e46dae1b2273814ef80231f86d59cadf94ec36fd757045ed713c5b75cde7/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245", size = 2534855 }, + { url = "https://files.pythonhosted.org/packages/0e/6d/e97245eabff29d7c2de5fc1fc17cf7ef427beee93d20a5ae114c6e6718bd/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e", size = 2486614 }, + { url = "https://files.pythonhosted.org/packages/70/a7/2cd2c9d5e23b556c11e3b7da41895808d9b056f8f34f50de4375a35b4951/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f", size = 2454928 }, + { url = "https://files.pythonhosted.org/packages/63/41/815d19767e2adb1a585213b801c954f46102f305c352c4a4f96287342d44/psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682", size = 1025249 }, + { url = "https://files.pythonhosted.org/packages/5e/4c/9233e0e206634a5387f3ab40f334a5325fb8bef2ca4e12ee7dbdeaf96afc/psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0", size = 1163645 }, +] + +[[package]] +name = "pycodestyle" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/56/52d8283e1a1c85695291040192776931782831e21117c84311cbdd63f70c/pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c", size = 39055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/c4/bf8ede2d1641e0a2e027c6d0c7060e00332851ea772cc5cee42a4a207707/pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4", size = 31221 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "pyjwt" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/72/8259b2bccfe4673330cea843ab23f86858a419d8f1493f66d413a76c7e3b/PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", size = 78313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/4f/e04a8067c7c96c364cef7ef73906504e2f40d690811c021e1a1901473a19/PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320", size = 22591 }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pytest" +version = "8.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/58/e993ca5357553c966b9e73cb3475d9c935fe9488746e13ebdf9b80fae508/pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977", size = 1427980 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/e7/81ebdd666d3bff6670d27349b5053605d83d55548e6bd5711f3b0ae7dd23/pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343", size = 339873 }, +] + +[[package]] +name = "pytest-env" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/cc/df6940b2527bfa634c00940dfb6e3ec873bdfb7507b55894c93283fa3178/pytest_env-1.1.3.tar.gz", hash = "sha256:fcd7dc23bb71efd3d35632bde1bbe5ee8c8dc4489d6617fb010674880d96216b", size = 8627 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/b2/bdc663a5647ce2034f7e8420122af340df87c01ba97745fc753b8c917acb/pytest_env-1.1.3-py3-none-any.whl", hash = "sha256:aada77e6d09fcfb04540a6e462c58533c37df35fa853da78707b17ec04d17dfc", size = 6154 }, +] + +[[package]] +name = "pytest-flask" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "pytest" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/23/32b36d2f769805c0f3069ca8d9eeee77b27fcf86d41d40c6061ddce51c7d/pytest-flask-1.3.0.tar.gz", hash = "sha256:58be1c97b21ba3c4d47e0a7691eb41007748506c36bf51004f78df10691fa95e", size = 35816 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/03/7a917fda3d0e96b4e80ab1f83a6628ec4ee4a882523b49417d3891bacc9e/pytest_flask-1.3.0-py3-none-any.whl", hash = "sha256:c0e36e6b0fddc3b91c4362661db83fa694d1feb91fa505475be6732b5bc8c253", size = 13105 }, +] + +[[package]] +name = "pytest-html" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pytest" }, + { name = "pytest-metadata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/ab/4862dcb5a8a514bd87747e06b8d55483c0c9e987e1b66972336946e49b49/pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07", size = 150773 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c7/c160021cbecd956cc1a6f79e5fe155f7868b2e5b848f1320dad0b3e3122f/pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71", size = 23491 }, +] + +[[package]] +name = "pytest-metadata" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428 }, +] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "python-json-logger" +version = "2.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/da/95963cebfc578dabd323d7263958dfb68898617912bb09327dd30e9c8d13/python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c", size = 10508 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/a6/145655273568ee78a581e734cf35beb9e33a370b29c5d3c8fee3744de29f/python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd", size = 8067 }, +] + +[[package]] +name = "python-multipart" +version = "0.0.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/0f/9c55ac6c84c0336e22a26fa84ca6c51d58d7ac3a2d78b0dfa8748826c883/python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026", size = 31516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/47/444768600d9e0ebc82f8e347775d24aef8f6348cf00e9fa0e81910814e6d/python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215", size = 22299 }, +] + +[[package]] +name = "pytz" +version = "2024.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/26/9f1f00a5d021fff16dee3de13d43e5e978f3d58928e129c3a62cf7eb9738/pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", size = 316214 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/3d/a121f284241f08268b21359bd425f7d4825cffc5ac5cd0e1b3d82ffd2b10/pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319", size = 505474 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", size = 125201 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/06/4beb652c0fe16834032e54f0956443d4cc797fe645527acee59e7deaa0a2/PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", size = 189447 }, + { url = "https://files.pythonhosted.org/packages/5b/07/10033a403b23405a8fc48975444463d3d10a5c2736b7eb2550b07b367429/PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f", size = 169264 }, + { url = "https://files.pythonhosted.org/packages/f1/26/55e4f21db1f72eaef092015d9017c11510e7e6301c62a6cfee91295d13c6/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", size = 677003 }, + { url = "https://files.pythonhosted.org/packages/ba/91/090818dfa62e85181f3ae23dd1e8b7ea7f09684864a900cab72d29c57346/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", size = 699070 }, + { url = "https://files.pythonhosted.org/packages/29/61/bf33c6c85c55bc45a29eee3195848ff2d518d84735eb0e2d8cb42e0d285e/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", size = 705525 }, + { url = "https://files.pythonhosted.org/packages/07/91/45dfd0ef821a7f41d9d0136ea3608bb5b1653e42fd56a7970532cb5c003f/PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", size = 707514 }, + { url = "https://files.pythonhosted.org/packages/b6/a0/b6700da5d49e9fed49dc3243d3771b598dad07abb37cc32e524607f96adc/PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", size = 130488 }, + { url = "https://files.pythonhosted.org/packages/24/97/9b59b43431f98d01806b288532da38099cc6f2fea0f3d712e21e269c0279/PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", size = 145338 }, +] + +[[package]] +name = "redis" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/88/63d802c2b18dd9eaa5b846cbf18917c6b2882f20efda398cc16a7500b02c/redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d", size = 4561721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/2e/409703d645363352a20c944f5d119bdae3eb3034051a53724a7c5fee12b8/redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c", size = 241149 }, +] + +[[package]] +name = "referencing" +version = "0.31.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/ce/e99def6196f53af8de12a9c36968de32f80b7871084d677d0dfcd2762d0b/referencing-0.31.1.tar.gz", hash = "sha256:81a1471c68c9d5e3831c30ad1dd9815c45b558e596653db751a2bfdd17b3b9ec", size = 54177 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/d8/e826b3f743d97e45d3ace674a5c6f026069428e608c5fde3d08d072c87f2/referencing-0.31.1-py3-none-any.whl", hash = "sha256:c19c4d006f1757e3dd75c4f784d38f8698d87b649c54f9ace14e5e8c9667c01d", size = 25842 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rich" +version = "12.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "commonmark" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/23/814edf09ec6470d52022b9e95c23c1bef77f0bc451761e1504ebd09606d3/rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0", size = 220114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/60/81ac2e7d1e3b861ab478a72e3b20fc91c4302acd2274822e493758941829/rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e", size = 237505 }, +] + +[[package]] +name = "rpds-py" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/aa/e7c404bdee1db7be09860dff423d022ffdce9269ec8e6532cce09ee7beea/rpds_py-0.18.1.tar.gz", hash = "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f", size = 25388 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/eb/5b7591bb8d9f710df243a3b6304a2b70db5a426a2bd478c2912f8b81b806/rpds_py-0.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53", size = 327723 }, + { url = "https://files.pythonhosted.org/packages/b9/9a/f1cce2481968d0ff1301d6da02bb977d7c7dc2ad9d218281ead38cc7f1ae/rpds_py-0.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80", size = 322269 }, + { url = "https://files.pythonhosted.org/packages/f3/16/7ddc46210ec4b52614c5d5ed72ca0afd12b6c6af1d7cb3859b2e7faa8ffe/rpds_py-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9", size = 1114128 }, + { url = "https://files.pythonhosted.org/packages/fd/6a/e67b83791863db607a297b822fe95813c6cbff979608496f47d81ad45fea/rpds_py-0.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d", size = 1123687 }, + { url = "https://files.pythonhosted.org/packages/d7/a9/b25013071a61f008a8266a208e701cf8ec2c2946feb6005b3ec454f63a66/rpds_py-0.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09", size = 1145179 }, + { url = "https://files.pythonhosted.org/packages/57/65/b9769f891d0f2f915151f6d172f82ce182cedf950bcc65af4e853d794421/rpds_py-0.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944", size = 1309609 }, + { url = "https://files.pythonhosted.org/packages/e5/20/10c12b1acb102c4981a7e1dc86b60e36c1d5c940a7bda48643542f80dbff/rpds_py-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0", size = 1114172 }, + { url = "https://files.pythonhosted.org/packages/73/5b/bf77d1fe5025eeec85d62e389edacf073b93553b4837f8221093acc3ed7e/rpds_py-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d", size = 1139248 }, + { url = "https://files.pythonhosted.org/packages/c2/b9/dcb20646cda07b4e9db3b346c19a4685623c9a9aa8ad8a566776def0da33/rpds_py-0.18.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60", size = 1277001 }, + { url = "https://files.pythonhosted.org/packages/55/5c/f59ed857a85d6713d936d70e3235a7c9bc51bc83ab7c1b4e9b4f9371abbc/rpds_py-0.18.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da", size = 1304195 }, + { url = "https://files.pythonhosted.org/packages/4f/3c/2807bb396f1d940813d1ec39efb8984ec01e84e2064db9a06bf314f3658d/rpds_py-0.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1", size = 1282968 }, + { url = "https://files.pythonhosted.org/packages/d2/13/495eea6921b280ac04602fc3cc4b385ab985a2eb3e450281d02ce98872bc/rpds_py-0.18.1-cp310-none-win32.whl", hash = "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333", size = 196635 }, + { url = "https://files.pythonhosted.org/packages/1b/bf/c8f8b5d1da7f0673998c63d2246987773c3422e1c2482bbf511b7fffe184/rpds_py-0.18.1-cp310-none-win_amd64.whl", hash = "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a", size = 209023 }, + { url = "https://files.pythonhosted.org/packages/97/04/966a1b2286d6af7ab00bf66ccd18cac38c59b0c2973be18975edb19c639f/rpds_py-0.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e", size = 326661 }, + { url = "https://files.pythonhosted.org/packages/75/e6/3a04f482d8c6d602d6d848ce18e6195510504fed6ff32928052321fcca72/rpds_py-0.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65", size = 321065 }, + { url = "https://files.pythonhosted.org/packages/c0/96/edfe0d2cb019aab199344d19a2c0e2e3514ffeb67a04236933630c8a4090/rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae", size = 1114055 }, + { url = "https://files.pythonhosted.org/packages/05/48/b578893a32290c9011e93e340264fdefa0df0f074d793a8c419e707fe346/rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de", size = 1123145 }, + { url = "https://files.pythonhosted.org/packages/a6/71/f4e8ac7a833ff6f70e18f6d2496b1a1d3a08272c777624359d2aa785de45/rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f", size = 1144650 }, + { url = "https://files.pythonhosted.org/packages/cb/61/90bb60a78c7c5da7155fed66b6cc875b9b402108565a00057f45391f3dcc/rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397", size = 1306828 }, + { url = "https://files.pythonhosted.org/packages/79/f4/e91e3d9c462387c08b833687c7095967461b785ac52e95eaa4d928a459d8/rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843", size = 1116549 }, + { url = "https://files.pythonhosted.org/packages/47/c2/c711866156543ada46d5977383235d4c7821bb27db108014f4895d18fc9c/rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163", size = 1138719 }, + { url = "https://files.pythonhosted.org/packages/a9/60/cc3d345d125998ecbccb9ab394193243c66903d53b02beade693810563fa/rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346", size = 1275986 }, + { url = "https://files.pythonhosted.org/packages/92/00/426001ad8c36f1a9a76cc414489f3eab6750f34cf1fee5ec054dba8af07f/rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c", size = 1306612 }, + { url = "https://files.pythonhosted.org/packages/07/e9/89e1f70ee6e32fd2c7f0829d9264b28683bcb4ddb54bcfff0fa4506bf629/rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4", size = 1281640 }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython' and python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/81/4dfc17eb6ebb1aac314a3eb863c1325b907863a1b8b1382cdffcb6ac0ed9/ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b", size = 143362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/67/8ece580cc363331d9a53055130f86b096bf16e38156e33b1d3014fffda6b/ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", size = 117761 }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/ab/bab9eb1566cd16f060b54055dd39cf6a34bfa0240c53a7218c43e974295b/ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", size = 213824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/01/37ac131614f71b98e9b148b2d7790662dcee92217d2fb4bac1aa377def33/ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", size = 148236 }, + { url = "https://files.pythonhosted.org/packages/61/ee/4874c9fc96010fce85abefdcbe770650c5324288e988d7a48b527a423815/ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", size = 133996 }, + { url = "https://files.pythonhosted.org/packages/d3/62/c60b034d9a008bbd566eeecf53a5a4c73d191c8de261290db6761802b72d/ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412", size = 526680 }, + { url = "https://files.pythonhosted.org/packages/90/8c/6cdb44f548b29eb6328b9e7e175696336bc856de2ff82e5776f860f03822/ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", size = 605853 }, + { url = "https://files.pythonhosted.org/packages/88/30/fc45b45d5eaf2ff36cffd215a2f85e9b90ac04e70b97fd4097017abfb567/ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", size = 655206 }, + { url = "https://files.pythonhosted.org/packages/af/dc/133547f90f744a0c827bac5411d84d4e81da640deb3af1459e38c5f3b6a0/ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", size = 689649 }, + { url = "https://files.pythonhosted.org/packages/23/1d/589139191b187a3c750ae8d983c42fd799246d5f0dd84451a0575c9bdbe9/ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", size = 100044 }, + { url = "https://files.pythonhosted.org/packages/4f/5b/744df20285a75ac4c606452ce9a0fcc42087d122f42294518ded1017697c/ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", size = 117825 }, +] + +[[package]] +name = "s3transfer" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/67/94c6730ee4c34505b14d94040e2f31edf144c230b6b49e971b4f25ff8fab/s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6", size = 144095 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/4a/b221409913760d26cf4498b7b1741d510c82d3ad38381984a3ddc135ec66/s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69", size = 82716 }, +] + +[[package]] +name = "sentry-sdk" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/8c/fa54692542bc11649a3590d1ba1f455fba9986758048b2ecfee8498cfaf9/sentry_sdk-2.9.0.tar.gz", hash = "sha256:4c85bad74df9767976afb3eeddc33e0e153300e887d637775a753a35ef99bee6", size = 276392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/22/249d158f9497231cd24e2829049b2c3e82841ec81b0a98b6f722357d5fed/sentry_sdk-2.9.0-py2.py3-none-any.whl", hash = "sha256:0bea5fa8b564cc0d09f2e6f55893e8f70286048b0ffb3a341d5b695d1af0e6ee", size = 301811 }, +] + +[package.optional-dependencies] +flask = [ + { name = "blinker" }, + { name = "flask" }, + { name = "markupsafe" }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "soupsieve" +version = "2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/21/952a240de1c196c7e3fbcd4e559681f0419b1280c617db21157a0390717b/soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690", size = 100943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/f3/038b302fdfbe3be7da016777069f26ceefe11a681055ea1f7817546508e3/soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7", size = 36131 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.30" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/d0/0137ebcf0dc230c2e82a621b3af755b8788a2a9dd6fd1b8cd6d5e7f6b00d/SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255", size = 9579500 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/d0/aec1421ff832da60badef9cf01fdc795b2ea399c5d65e2b8c37d801d06ff/SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc", size = 2082300 }, + { url = "https://files.pythonhosted.org/packages/be/86/25faae6b5c9920a7954bf7c68a7ff8f3436e9f140ac7dfccc8fc213bce66/SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2", size = 2073500 }, + { url = "https://files.pythonhosted.org/packages/22/da/90e8d421836c2d265b7a72a7923348705d1e0124321bb2b3f2de307b91d0/SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7", size = 3055759 }, + { url = "https://files.pythonhosted.org/packages/c2/83/2ed47c5b841496d4e106f8ed04316c6193ba8615ab70fe769a593237b20b/SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797", size = 3064230 }, + { url = "https://files.pythonhosted.org/packages/a5/3f/b3f1bc1f14ab65ab1d8b4030ba0787b529bfde654b2b2cf9eb7cdb26b28c/SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af", size = 3098327 }, + { url = "https://files.pythonhosted.org/packages/8c/e4/3550fba0561560cb8fae78d3636813f7a2b83eb7b55c76703ac34143cd3c/SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5", size = 3096450 }, + { url = "https://files.pythonhosted.org/packages/18/bc/e6117ae5cc3577fb3ee487f6321802ec72dcdd81fca13baf0db907818ad3/SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8", size = 2052510 }, + { url = "https://files.pythonhosted.org/packages/f2/6b/18900a4df0d91397569f645105a4fb36f12033075622e3d131c456dc73f3/SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb", size = 2077661 }, + { url = "https://files.pythonhosted.org/packages/de/80/13fc9c003dffc169e03244e0ce23495ff54bbd77ba1245ef01c9a5c04a4c/SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a", size = 1873477 }, +] + +[package.optional-dependencies] +mypy = [ + { name = "mypy" }, +] + +[[package]] +name = "sqlalchemy-json" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/57/73ba3d0ee5efbec5a0d15ee3e21606edd33b1d1fd11b5d64e581c8b8a3f6/sqlalchemy-json-0.7.0.tar.gz", hash = "sha256:620d0b26f648f21a8fa9127df66f55f83a5ab4ae010e5397a5c6989a08238561", size = 8848 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/79/1fc7309ecf75756e9ad9280f19cd83ca7b79a0dae36cd025f668e8e2741f/sqlalchemy_json-0.7.0-py3-none-any.whl", hash = "sha256:27881d662ca18363a4ac28175cc47ea2a6f2bef997ae1159c151026b741818e6", size = 7688 }, +] + +[[package]] +name = "sqlalchemy-utils" +version = "0.41.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/bf/abfd5474cdd89ddd36dbbde9c6efba16bfa7f5448913eba946fed14729da/SQLAlchemy-Utils-0.41.2.tar.gz", hash = "sha256:bc599c8c3b3319e53ce6c5c3c471120bd325d0071fb6f38a10e924e3d07b9990", size = 138017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/f0/dc4757b83ac1ab853cf222df8535ed73973e0c203d983982ba7b8bc60508/SQLAlchemy_Utils-0.41.2-py3-none-any.whl", hash = "sha256:85cf3842da2bf060760f955f8467b87983fb2e30f1764fd0e24a48307dc8ec6e", size = 93083 }, +] + +[[package]] +name = "starlette" +version = "0.37.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/b5/6bceb93ff20bd7ca36e6f7c540581abb18f53130fabb30ba526e26fd819b/starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823", size = 2843736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/18/31fa32ed6c68ba66220204ef0be798c349d0a20c1901f9d4a794e08c76d8/starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee", size = 71908 }, +] + +[[package]] +name = "swagger-ui-bundle" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/e6/d8ae21087a42627c2a04a738c947825b78c26b18595704b94bd3227197a2/swagger_ui_bundle-1.1.0.tar.gz", hash = "sha256:20673c3431c8733d5d1615ecf79d9acf30cff75202acaf21a7d9c7f489714529", size = 2599741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/66/8fb11445940bde7ca328d6aa23dd36b6056197d862f4bd6bb51c820c50e5/swagger_ui_bundle-1.1.0-py3-none-any.whl", hash = "sha256:f7526f7bb99923e10594c54247265839bec97e96b0438561ac86faf40d40dd57", size = 2626591 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/6d/fa469ae21497ddc8bc93e5877702dca7cb8f911e337aca7452b5724f1bb6/urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168", size = 292266 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/1c/89ffc63a9605b583d5df2be791a27bc1a42b7c32bab68d3c8f2f73a98cd4/urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", size = 121444 }, +] + +[[package]] +name = "uvicorn" +version = "0.30.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/16/9f5ccaa1a76e5bfbaa0c67640e2db8a5214ca08d92a1b427fa1677b3da88/uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8", size = 42572 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/f9/e6f30ba6094733e4f9794fd098ca0543a19b07ac1fa3075d595bf0f1fb60/uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81", size = 62393 }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/16/728cc5dde368e6eddb299c5aec4d10eaf25335a5af04e8c0abd68e2e9d32/uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd", size = 2318492 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/c2/27bf858a576b1fa35b5c2c2029c8cec424a8789e87545ed2f25466d1f21d/uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e", size = 1443484 }, + { url = "https://files.pythonhosted.org/packages/4e/35/05b6064b93f4113412d1fd92bdcb6018607e78ae94d1712e63e533f9b2fa/uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428", size = 793850 }, + { url = "https://files.pythonhosted.org/packages/aa/56/b62ab4e10458ce96bb30c98d327c127f989d3bb4ef899e4c410c739f7ef6/uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8", size = 3418601 }, + { url = "https://files.pythonhosted.org/packages/ab/ed/12729fba5e3b7e02ee70b3ea230b88e60a50375cf63300db22607694d2f0/uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849", size = 3416731 }, + { url = "https://files.pythonhosted.org/packages/a2/23/80381a2d728d2a0c36e2eef202f5b77428990004d8fbdd3865558ff49fa5/uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957", size = 4128572 }, + { url = "https://files.pythonhosted.org/packages/6b/23/1ee41a15e1ad15182e2bd12cbfd37bcb6802f01d6bbcaddf6ca136cbb308/uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd", size = 4129235 }, +] + +[[package]] +name = "virtualenv" +version = "20.26.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/5a/cabd5846cb550e2871d9532def625d0771f4e38f765c30dc0d101be33697/virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c", size = 7290363 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/02/085eee8570e807db9a1aa905e18c9123efec753ae9021b029115c6e0bb28/virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b", size = 3917916 }, +] + +[[package]] +name = "watchfiles" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/e1/666771f0746f95c4df767ff98ff17fe55bb0c32ac88ec14ce0615a789330/watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb", size = 37900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/1e/040d62f23f02dea6903edd810ccbab04115fbc3862c2ce37fa31323b5d83/watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538", size = 394988 }, + { url = "https://files.pythonhosted.org/packages/29/21/59afadc00e3f6cc18aa507f7ae5fef1d6bc864dcc35c595f06d012433fc0/watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e", size = 390956 }, + { url = "https://files.pythonhosted.org/packages/8f/f1/ea873aefbba72880e028c3efb7d846b5ed01143c4a4b08049453993e8a52/watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1", size = 1196698 }, + { url = "https://files.pythonhosted.org/packages/bf/2e/c0d43c72b33d9520d6d9d10a65be0b680507df6c8316e28dd8f4a06b90f9/watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a", size = 1210192 }, + { url = "https://files.pythonhosted.org/packages/dc/07/aada2dc1aa6b184aeee67ecd079d5a38edd5320f866ccc1539e337c82b8a/watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd", size = 1229778 }, + { url = "https://files.pythonhosted.org/packages/b2/e6/c853384d143001f3f698aae27ce40eeaac3e5f09aa63e671212972843600/watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb", size = 1234220 }, + { url = "https://files.pythonhosted.org/packages/8b/57/92c29fec82104277454b6082824d83b434a6833ab0c87afc8b60970c4c98/watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171", size = 1366736 }, + { url = "https://files.pythonhosted.org/packages/3d/ae/e7eddbdca559f14a9a38cf04782a5d715cf350aad498d0862fb02b4ebe10/watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71", size = 1198604 }, + { url = "https://files.pythonhosted.org/packages/a7/f6/a2673bbbdb2943b37e8587719bf14d6f544d00660f059c66e5c034b279e0/watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39", size = 1364604 }, + { url = "https://files.pythonhosted.org/packages/1b/46/7a4f7231ccf82f814282e5ca7fa1ab6bcd8efbbea98be75e0126e53edcad/watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848", size = 1367627 }, + { url = "https://files.pythonhosted.org/packages/71/10/a020809df82b46f5d30e7a8cfdebcec1eadc572aa06a60f42e50495c52c3/watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797", size = 272397 }, + { url = "https://files.pythonhosted.org/packages/f8/79/9d7e16e805c721d5e5a3b1d1a3e798cc9b1282b799d7058a70ab506dc970/watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb", size = 282074 }, + { url = "https://files.pythonhosted.org/packages/7c/86/9bc03462970d5002863919cdcbaa2d8be8c9a6ef59188cfe7a5f596c80a3/watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68", size = 395790 }, + { url = "https://files.pythonhosted.org/packages/50/c2/0022e988ce16c6dc4d2903de6ca26c9c9dbc8ee5658859f04f20146c7871/watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c", size = 392212 }, + { url = "https://files.pythonhosted.org/packages/ec/e5/f50e27bea4b408d1b902d4b0e67a1a2502c85eef91192178db6c0e8f2ccd/watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab", size = 1197263 }, + { url = "https://files.pythonhosted.org/packages/27/fe/5d9e484caade79e1b54aebe48eeae9f74df92826fffb797700e8bd78cd09/watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2", size = 1199104 }, +] + +[[package]] +name = "websockets" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/b9/360b86ded0920a93bff0db4e4b0aa31370b0208ca240b2e98d62aad8d082/websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", size = 124025 }, + { url = "https://files.pythonhosted.org/packages/bb/d3/1eca0d8fb6f0665c96f0dc7c0d0ec8aa1a425e8c003e0c18e1451f65d177/websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", size = 121261 }, + { url = "https://files.pythonhosted.org/packages/4e/e1/f6c3ecf7f1bfd9209e13949db027d7fdea2faf090c69b5f2d17d1d796d96/websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", size = 121328 }, + { url = "https://files.pythonhosted.org/packages/74/4d/f88eeceb23cb587c4aeca779e3f356cf54817af2368cb7f2bd41f93c8360/websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", size = 130925 }, + { url = "https://files.pythonhosted.org/packages/16/17/f63d9ee6ffd9afbeea021d5950d6e8db84cd4aead306c6c2ca523805699e/websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", size = 129930 }, + { url = "https://files.pythonhosted.org/packages/9a/12/c7a7504f5bf74d6ee0533f6fc7d30d8f4b79420ab179d1df2484b07602eb/websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", size = 130245 }, + { url = "https://files.pythonhosted.org/packages/e4/6a/3600c7771eb31116d2e77383d7345618b37bb93709d041e328c08e2a8eb3/websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", size = 134966 }, + { url = "https://files.pythonhosted.org/packages/22/26/df77c4b7538caebb78c9b97f43169ef742a4f445e032a5ea1aaef88f8f46/websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", size = 134196 }, + { url = "https://files.pythonhosted.org/packages/e5/18/18ce9a4a08203c8d0d3d561e3ea4f453daf32f099601fc831e60c8a9b0f2/websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", size = 134822 }, + { url = "https://files.pythonhosted.org/packages/45/51/1f823a341fc20a880e67ae62f6c38c4880a24a4b60fbe544a38f516f39a1/websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", size = 124454 }, + { url = "https://files.pythonhosted.org/packages/41/b0/5ec054cfcf23adfc88d39359b85e81d043af8a141e3ac8ce40f45a5ce5f4/websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", size = 124974 }, + { url = "https://files.pythonhosted.org/packages/43/8b/554a8a8bb6da9dd1ce04c44125e2192af7b7beebf6e3dbfa5d0e285cc20f/websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", size = 121110 }, + { url = "https://files.pythonhosted.org/packages/b0/8e/58b8812940d746ad74d395fb069497255cb5ef50748dfab1e8b386b1f339/websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", size = 123216 }, + { url = "https://files.pythonhosted.org/packages/81/ee/272cb67ace1786ce6d9f39d47b3c55b335e8b75dd1972a7967aad39178b6/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", size = 122821 }, + { url = "https://files.pythonhosted.org/packages/a8/03/387fc902b397729df166763e336f4e5cec09fe7b9d60f442542c94a21be1/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", size = 122768 }, + { url = "https://files.pythonhosted.org/packages/50/f0/5939fbc9bc1979d79a774ce5b7c4b33c0cefe99af22fb70f7462d0919640/websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", size = 125009 }, + { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370 }, +] + +[[package]] +name = "werkzeug" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/51/2e0fc149e7a810d300422ab543f87f2bcf64d985eb6f1228c4efd6e4f8d4/werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", size = 803342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274 }, +] diff --git a/pyproject.toml b/pyproject.toml index b18de566d..3a9b6de24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ exclude = [ "venv*", ".venv*", "__pycache__", + "fund_store/config/fund_loader_config/FAB/" ] mccabe.max-complexity = 12 diff --git a/pytest.ini b/pytest.ini index d293c6dd4..65f2c33ce 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,3 +3,5 @@ env = FLASK_ENV=unit_test FLASK_DEBUG=1 GITHUB_SHA=123123 +testpaths = + tests