diff --git a/.env.example b/.env.example deleted file mode 100644 index 5ffc13da8ca419c80446b36a3b7afd26fcd13a68..0000000000000000000000000000000000000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -SQLALCHEMY_DATABASE_URI=sqlite+aiosqlite:///:memory: \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index baad420b326f9f25bfccb8ca72f86b77c61126c6..0000000000000000000000000000000000000000 --- a/.gitignore +++ /dev/null @@ -1,140 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index dc0b1af7f12a5d4c8451aac35648cc3dbeeb5311..0000000000000000000000000000000000000000 --- a/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -# Use official Python 3.12 slim image -FROM python:3.12-slim - -# Set environment variables -ENV SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://avnadmin:AVNS_W4PzkCRVJw0ASHFYc8i@pg-5cdc7c9-aryanjain19082002-7775.j.aivencloud.com:25607/defaultdb -ENV PYTHONUNBUFFERED=1 -ENV POETRY_VERSION=1.8.2 - -# Set work directory -WORKDIR /app - -# Install system dependencies -RUN apt-get update && apt-get install -y \ - curl \ - build-essential \ - libreoffice \ - default-jre-headless \ - fonts-dejavu \ - fonts-liberation \ - fontconfig \ - xvfb \ - dbus-x11 \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - - -# Install Poetry -RUN curl -sSL https://install.python-poetry.org | python3 - -ENV PATH="/root/.local/bin:$PATH" - -# Copy only dependency files first to leverage Docker cache -COPY pyproject.toml poetry.lock* /app/ - -# Install dependencies (without creating a virtualenv) -RUN poetry config virtualenvs.create false \ - && poetry install --no-interaction --no-ansi - -RUN mkdir -p /tmp/.config/libreoffice && \ - chmod 755 /tmp/.config/libreoffice - -RUN mkdir -p /files && chmod 777 /files - -ENV HOME=/tmp \ - TMPDIR=/tmp \ - JAVA_HOME=/usr/lib/jvm/default-java - -# Copy the entire project -COPY . /app/ - -# Expose the port Uvicorn will run on -EXPOSE 7860 - -# Run the FastAPI application -CMD ["uvicorn", "src.app:app", "--reload", "--host", "0.0.0.0", "--port", "7860"] \ No newline at end of file diff --git a/alembic.ini b/alembic.ini deleted file mode 100644 index 687fd2e231f7a0e4206b281552d352539c97074b..0000000000000000000000000000000000000000 --- a/alembic.ini +++ /dev/null @@ -1,141 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts. -# this is typically a path given in POSIX (e.g. forward slashes) -# format, relative to the token %(here)s which refers to the location of this -# ini file -script_location = %(here)s/alembic - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. for multiple paths, the path separator -# is defined by "path_separator" below. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to /versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "path_separator" -# below. -# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions - -# path_separator; This indicates what character is used to split lists of file -# paths, including version_locations and prepend_sys_path within configparser -# files such as alembic.ini. -# The default rendered in new alembic.ini files is "os", which uses os.pathsep -# to provide os-dependent path splitting. -# -# Note that in order to support legacy alembic.ini files, this default does NOT -# take place if path_separator is not present in alembic.ini. If this -# option is omitted entirely, fallback logic is as follows: -# -# 1. Parsing of the version_locations option falls back to using the legacy -# "version_path_separator" key, which if absent then falls back to the legacy -# behavior of splitting on spaces and/or commas. -# 2. Parsing of the prepend_sys_path option falls back to the legacy -# behavior of splitting on spaces, commas, or colons. -# -# Valid values for path_separator are: -# -# path_separator = : -# path_separator = ; -# path_separator = space -# path_separator = newline -# -# Use os.pathsep. Default configuration used for new projects. -path_separator = os - - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -# database URL. This is consumed by the user-maintained env.py script only. -# other means of configuring database URLs may be customized within the env.py -# file. -sqlalchemy.url = driver://user:pass@host/db - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the exec runner, execute a binary -# hooks = ruff -# ruff.type = exec -# ruff.executable = %(here)s/.venv/bin/ruff -# ruff.options = check --fix REVISION_SCRIPT_FILENAME - -# Logging configuration. This is also consumed by the user-maintained -# env.py script only. -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARNING -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARNING -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/alembic/README b/alembic/README deleted file mode 100644 index e0d0858f266ec27b243e8b92301fc7002e1f2745..0000000000000000000000000000000000000000 --- a/alembic/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration with an async dbapi. \ No newline at end of file diff --git a/alembic/env.py b/alembic/env.py deleted file mode 100644 index 74a811d9186bfcee8b8ed56ac557144553737f5a..0000000000000000000000000000000000000000 --- a/alembic/env.py +++ /dev/null @@ -1,95 +0,0 @@ -import asyncio -from logging.config import fileConfig - -from alembic import context -from sqlalchemy import pool -from sqlalchemy.engine import Connection -from sqlalchemy.ext.asyncio import async_engine_from_config - -from src.models import * - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = Base.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline() -> None: - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def do_run_migrations(connection: Connection) -> None: - context.configure(connection=connection, target_metadata=target_metadata) - - with context.begin_transaction(): - context.run_migrations() - - -async def run_async_migrations() -> None: - """In this scenario we need to create an Engine - and associate a connection with the context. - - """ - - connectable = async_engine_from_config( - config.get_section(config.config_ini_section, {}), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - - async with connectable.connect() as connection: - await connection.run_sync(do_run_migrations) - - await connectable.dispose() - - -def run_migrations_online() -> None: - """Run migrations in 'online' mode.""" - - connectable = config.attributes.get("connection", None) - - if connectable is None: - asyncio.run(run_async_migrations()) - else: - do_run_migrations(connectable) - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() \ No newline at end of file diff --git a/alembic/script.py.mako b/alembic/script.py.mako deleted file mode 100644 index 480b130d632ca677c11f23d9fe82cf4014d15e0c..0000000000000000000000000000000000000000 --- a/alembic/script.py.mako +++ /dev/null @@ -1,28 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision: str = ${repr(up_revision)} -down_revision: Union[str, None] = ${repr(down_revision)} -branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} -depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} - - -def upgrade() -> None: - """Upgrade schema.""" - ${upgrades if upgrades else "pass"} - - -def downgrade() -> None: - """Downgrade schema.""" - ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/0402a665d940_update_rfp_table.py b/alembic/versions/0402a665d940_update_rfp_table.py deleted file mode 100644 index 23a5a71816deaaf828d9ed13bcf583f3773c9397..0000000000000000000000000000000000000000 --- a/alembic/versions/0402a665d940_update_rfp_table.py +++ /dev/null @@ -1,28 +0,0 @@ -"""update rfp table - -Revision ID: 0402a665d940 -Revises: c70330d677c0 -Create Date: 2025-06-04 10:36:51.925465 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '0402a665d940' -down_revision: Union[str, None] = 'c70330d677c0' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.add_column("rfps", sa.Column("target_award_date", sa.DateTime(), nullable=True)) - - -def downgrade() -> None: - """Downgrade schema.""" - op.drop_column("rfps", "target_award_date") diff --git a/alembic/versions/11d43047d470_initial_tables.py b/alembic/versions/11d43047d470_initial_tables.py deleted file mode 100644 index 402826015a6ab2ffbd39cb06f714303006093f2b..0000000000000000000000000000000000000000 --- a/alembic/versions/11d43047d470_initial_tables.py +++ /dev/null @@ -1,118 +0,0 @@ -"""Initial tables - -Revision ID: 11d43047d470 -Revises: -Create Date: 2025-05-29 13:59:12.209373 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - -from sqlalchemy.dialects import postgresql - - -# revision identifiers, used by Alembic. -revision: str = '11d43047d470' -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - 'rfps', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('rfp_number', sa.String(), nullable=True), - sa.Column('received_proposals', sa.Integer(), nullable=True), - sa.Column('evaluated_count', sa.Integer(), nullable=True), - sa.Column('awaiting_evaluation', sa.Integer(), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - - op.create_table( - 'proposals', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('rfp_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('rfps.id', ondelete="CASCADE"), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('status', sa.Enum('APPROVED', 'EVALUATED', 'IN_REVIEW', name='proposalstatus'), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - - op.create_table( - 'proposal_ai_analyses', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False), - sa.Column('ai_score', sa.Float(), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - - op.create_table( - 'proposal_strengths', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False), - sa.Column('strength_point', sa.String(), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - - op.create_table( - 'proposal_rejection_reasons', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False), - sa.Column('reason', sa.String(), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - - op.create_table( - 'proposal_weaknesses', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False), - sa.Column('weakness_point', sa.String(), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - - op.create_table( - 'proposal_improvements', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False), - sa.Column('improvement_point', sa.String(), nullable=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - - op.create_table( - 'proposal_detail_analyses', - sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False), - sa.Column('ai_assessment_result', sa.String(), nullable=True), - sa.Column('confidence', sa.Float(), nullable=True), - sa.Column('operation_criteria', sa.Enum( - 'MISSION_ALIGNMENT', 'TECHNICAL_FEASIBILITY', 'COST_EFFICIENCY', 'RISK_ASSESSMENT', - name='operationcriteria'), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - """Downgrade schema.""" - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('proposal_detail_analyses') - op.drop_table('proposal_improvements') - op.drop_table('proposal_weaknesses') - op.drop_table('proposal_rejection_reasons') - op.drop_table('proposal_strengths') - op.drop_table('proposal_ai_analyses') - op.drop_table('proposals') - op.drop_table('rfps') - # ### end Alembic commands ### diff --git a/alembic/versions/3ba37b484f31_update_enum_type.py b/alembic/versions/3ba37b484f31_update_enum_type.py deleted file mode 100644 index d6bd20877ff61f35530ed4005335802fa8804bd5..0000000000000000000000000000000000000000 --- a/alembic/versions/3ba37b484f31_update_enum_type.py +++ /dev/null @@ -1,56 +0,0 @@ -"""update enum type - -Revision ID: 3ba37b484f31 -Revises: 718e96a6869a -Create Date: 2025-06-05 10:04:03.238833 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = "3ba37b484f31" -down_revision: Union[str, None] = "718e96a6869a" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.execute( - "CREATE TYPE analysistype_new AS ENUM ('STRENGTHS', 'WEAKNESSES', 'DEFICIENCIES')" - ) - - op.execute( - """ - ALTER TABLE analysis - ALTER COLUMN analysis_type TYPE analysistype_new - USING analysis_type::text::analysistype_new - """ - ) - - op.execute("DROP TYPE analysistype") - - op.execute("ALTER TYPE analysistype_new RENAME TO analysistype") - - -def downgrade() -> None: - """Downgrade schema.""" - op.execute( - "CREATE TYPE analysistype_old AS ENUM ('STRENGHTS', 'WEAKNESSES', 'DEFICIENCIES')" - ) - - op.execute( - """ - ALTER TABLE analysis - ALTER COLUMN analysis_type TYPE analysistype_old - USING analysis_type::text::analysistype_old - """ - ) - - op.execute("DROP TYPE analysistype") - op.execute("ALTER TYPE analysistype_old RENAME TO analysistype") diff --git a/alembic/versions/718e96a6869a_update_tables.py b/alembic/versions/718e96a6869a_update_tables.py deleted file mode 100644 index 122320dd8d2d9a496790d69813af11feac0fe079..0000000000000000000000000000000000000000 --- a/alembic/versions/718e96a6869a_update_tables.py +++ /dev/null @@ -1,125 +0,0 @@ -"""update tables - -Revision ID: 718e96a6869a -Revises: a3bbab758ddb -Create Date: 2025-06-05 05:27:57.408552 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "718e96a6869a" -down_revision: Union[str, None] = "a3bbab758ddb" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.add_column("rfps", sa.Column("ko_name", sa.String(), nullable=True)) - op.add_column("rfps", sa.Column("award_type", sa.String(), nullable=True)) - op.add_column("rfps", sa.Column("google_drive_id", sa.String(), nullable=True)) - - op.add_column("proposals", sa.Column("ai_score", sa.Float(), nullable=True)) - op.add_column("proposals", sa.Column("final_score", sa.Float(), nullable=True)) - - op.create_table( - "comparative_weights", - sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column( - "rfp_id", - postgresql.UUID(as_uuid=True), - sa.ForeignKey("rfps.id", ondelete="CASCADE"), - nullable=False, - unique=True, - ), - sa.Column( - "technical_weight", - sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"), - nullable=False, - ), - sa.Column( - "management_weight", - sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"), - nullable=False, - ), - sa.Column( - "past_performance_weight", - sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"), - nullable=False, - ), - sa.Column( - "price_weight", - sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"), - nullable=False, - ), - sa.Column("strengths_weight", sa.Float(), nullable=False, default=15), - sa.Column("weaknesses_weight", sa.Float(), nullable=False), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - ) - - op.create_table( - "evaluations", - sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column( - "proposal_id", - postgresql.UUID(as_uuid=True), - sa.ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column( - "evaluation_type", - sa.Enum( - "TECHNICAL", - "MANAGEMENT", - "PAST_PERFORMANCE", - "PRICE", - name="evaluationtype", - ), - nullable=False, - ), - sa.Column("ai_score", sa.Float(), nullable=True), - sa.Column("adjusted_score", sa.Float(), nullable=True), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - ) - - op.create_table( - "analysis", - sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column( - "evaluation_id", - postgresql.UUID(as_uuid=True), - sa.ForeignKey("evaluations.id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column("insights", sa.Text(), nullable=True), - sa.Column( - "analysis_type", - sa.Enum("STRENGHTS", "WEAKNESSES", "DEFICIENCIES", name="analysistype"), - nullable=False, - ), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - ) - - -def downgrade() -> None: - """Downgrade schema.""" - op.drop_column("rfps", "ko_name") - op.drop_column("rfps", "award_type") - op.drop_column("rfps", "google_drive_id") - - op.drop_column("proposals", "ai_score") - op.drop_column("proposals", "final_score") - - op.drop_table("comparative_weights") - op.drop_table("evaluations") - op.drop_table("analysis") diff --git a/alembic/versions/7fae52a30825_update_rfp_table.py b/alembic/versions/7fae52a30825_update_rfp_table.py deleted file mode 100644 index 8fa5ced08a5279587b5a9c34f369c8017ba94bf9..0000000000000000000000000000000000000000 --- a/alembic/versions/7fae52a30825_update_rfp_table.py +++ /dev/null @@ -1,31 +0,0 @@ -"""update rfp table - -Revision ID: 7fae52a30825 -Revises: 0402a665d940 -Create Date: 2025-06-04 11:00:24.081115 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '7fae52a30825' -down_revision: Union[str, None] = '0402a665d940' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - status = sa.Enum("ACTIVE", "IN_PROGRESS", "NOT_YET_STARTED", "CUSTOMER_HOLD", "UNSOLVED_ISSUES", "AWARDED", "PROTESTED", name="rfpstatus") - status.create(op.get_bind()) - op.add_column("rfps", sa.Column("status", status, nullable=False, server_default="ACTIVE")) - - -def downgrade() -> None: - """Downgrade schema.""" - sa.Enum(name='rfpstatus').drop(op.get_bind(), checkfirst=True) - op.drop_column("rfps", "status") diff --git a/alembic/versions/7fb86c16841c_add_flag_in_rfp_table.py b/alembic/versions/7fb86c16841c_add_flag_in_rfp_table.py deleted file mode 100644 index 7b349a72d07555b3455c7f92cd2bcb97ee46207b..0000000000000000000000000000000000000000 --- a/alembic/versions/7fb86c16841c_add_flag_in_rfp_table.py +++ /dev/null @@ -1,36 +0,0 @@ -"""add flag in rfp table - -Revision ID: 7fb86c16841c -Revises: b5d2f8b83d7e -Create Date: 2025-06-06 11:06:57.084499 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '7fb86c16841c' -down_revision: Union[str, None] = 'b5d2f8b83d7e' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.add_column("rfps", sa.Column("flag", sa.Boolean(), nullable=True, server_default=sa.text("FALSE"))) - op.execute("UPDATE rfps SET flag = FALSE") - op.alter_column( - "rfps", - "flag", - existing_type=sa.Boolean(), - nullable=False, - server_default=sa.text("FALSE"), - ) - - -def downgrade() -> None: - """Downgrade schema.""" - op.drop_column("rfps", "flag") diff --git a/alembic/versions/80b53babd4e6_update_flag_column_name.py b/alembic/versions/80b53babd4e6_update_flag_column_name.py deleted file mode 100644 index 81cf0c3929f7c73824b406c06dc54300fb75cc29..0000000000000000000000000000000000000000 --- a/alembic/versions/80b53babd4e6_update_flag_column_name.py +++ /dev/null @@ -1,36 +0,0 @@ -"""update flag column name - -Revision ID: 80b53babd4e6 -Revises: 7fb86c16841c -Create Date: 2025-06-06 11:29:29.960724 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '80b53babd4e6' -down_revision: Union[str, None] = '7fb86c16841c' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.alter_column( - 'rfps', - 'flag', - new_column_name='comparative_weights_locked' - ) - - -def downgrade() -> None: - """Downgrade schema.""" - op.alter_column( - 'rfps', - 'comparative_weights_locked', - new_column_name='flag' - ) diff --git a/alembic/versions/a3bbab758ddb_categorize_the_analysis.py b/alembic/versions/a3bbab758ddb_categorize_the_analysis.py deleted file mode 100644 index e87ff38824662cbe87a173c67b0c6c13466f7575..0000000000000000000000000000000000000000 --- a/alembic/versions/a3bbab758ddb_categorize_the_analysis.py +++ /dev/null @@ -1,91 +0,0 @@ -"""categorize the analysis - -Revision ID: a3bbab758ddb -Revises: 7fae52a30825 -Create Date: 2025-06-04 14:01:24.366580 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = "a3bbab758ddb" -down_revision: Union[str, None] = "7fae52a30825" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - strength_category_enum = sa.Enum( - "TECHNICAL", "MANAGEMENT", "PAST_PERFORMANCE", "PRICE", name="strengthcategory" - ) - strength_category_enum.create(op.get_bind()) - op.add_column( - "proposal_strengths", - sa.Column( - "category", - strength_category_enum, - nullable=False, - server_default="TECHNICAL", - ), - ) - weakness_category_enum = sa.Enum( - "TECHNICAL", "MANAGEMENT", "PAST_PERFORMANCE", "PRICE", name="weaknesscategory" - ) - weakness_category_enum.create(op.get_bind()) - op.add_column( - "proposal_weaknesses", - sa.Column( - "category", - weakness_category_enum, - nullable=False, - server_default="TECHNICAL", - ), - ) - rejection_category_enum = sa.Enum( - "TECHNICAL", "MANAGEMENT", "PAST_PERFORMANCE", "PRICE", name="rejectioncategory" - ) - rejection_category_enum.create(op.get_bind()) - op.add_column( - "proposal_rejection_reasons", - sa.Column( - "category", - rejection_category_enum, - nullable=False, - server_default="TECHNICAL", - ), - ) - improvement_category_enum = sa.Enum( - "TECHNICAL", - "MANAGEMENT", - "PAST_PERFORMANCE", - "PRICE", - name="improvementcategory", - ) - improvement_category_enum.create(op.get_bind()) - op.add_column( - "proposal_improvements", - sa.Column( - "category", - improvement_category_enum, - nullable=False, - server_default="TECHNICAL", - ), - ) - - -def downgrade() -> None: - """Downgrade schema.""" - sa.Enum(name="strengthcategory").drop(op.get_bind(), checkfirst=True) - op.drop_column("proposal_strengths", "category") - sa.Enum(name="weaknesscategory").drop(op.get_bind(), checkfirst=True) - op.drop_column("proposal_weaknesses", "category") - sa.Enum(name="rejectioncategory").drop(op.get_bind(), checkfirst=True) - op.drop_column("proposal_rejection_reasons", "category") - sa.Enum(name="improvementcategory").drop(op.get_bind(), checkfirst=True) - op.drop_column("proposal_improvements", "category") diff --git a/alembic/versions/b5d2f8b83d7e_add_flag_and_status.py b/alembic/versions/b5d2f8b83d7e_add_flag_and_status.py deleted file mode 100644 index 6a0496cb0be6eeea689520455bd14b02f0dd1ed9..0000000000000000000000000000000000000000 --- a/alembic/versions/b5d2f8b83d7e_add_flag_and_status.py +++ /dev/null @@ -1,41 +0,0 @@ -"""add flag and status - -Revision ID: b5d2f8b83d7e -Revises: 3ba37b484f31 -Create Date: 2025-06-05 20:00:52.297117 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'b5d2f8b83d7e' -down_revision: Union[str, None] = '3ba37b484f31' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.add_column("comparative_weights", sa.Column("flag", sa.Boolean(), nullable=True, server_default=sa.text("FALSE"))) - op.execute("UPDATE comparative_weights SET flag = FALSE") - op.alter_column( - "comparative_weights", - "flag", - existing_type=sa.Boolean(), - nullable=False, - server_default=sa.text("FALSE"), - ) - analysis_enum = sa.Enum("ACCEPTED", "REJECTED", "PENDING", name="analysisstatus") - analysis_enum.create(op.get_bind()) - op.add_column("analysis", sa.Column("status", analysis_enum, nullable=False, server_default="PENDING")) - -def downgrade() -> None: - """Downgrade schema.""" - op.drop_column("comparative_weights", "flag") - - sa.Enum(name="analysisstatus").drop(op.get_bind(), checkfirst=True) - op.drop_column("analysis", "status") \ No newline at end of file diff --git a/alembic/versions/c70330d677c0_add_evaluation_criteria.py b/alembic/versions/c70330d677c0_add_evaluation_criteria.py deleted file mode 100644 index 262d33e0cfc4ba4748bfc98e15f6c7d1be2119c1..0000000000000000000000000000000000000000 --- a/alembic/versions/c70330d677c0_add_evaluation_criteria.py +++ /dev/null @@ -1,38 +0,0 @@ -"""add evaluation_criteria - -Revision ID: c70330d677c0 -Revises: e825902b2e8a -Create Date: 2025-06-02 19:38:53.073958 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - -from sqlalchemy.dialects import postgresql - - -# revision identifiers, used by Alembic. -revision: str = 'c70330d677c0' -down_revision: Union[str, None] = 'e825902b2e8a' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.create_table( - "evaluation_criteria_details", - sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column("rfp_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False), - sa.Column("evaluation_criteria", sa.String(), nullable=True), - sa.Column("evaluation_criteria_type", sa.Enum("EVALUATION", "GATE", name="evaluationcriteriatype"), nullable=False), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - ) - - -def downgrade() -> None: - """Downgrade schema.""" - op.drop_table("evaluation_criteria_details") diff --git a/alembic/versions/e825902b2e8a_alter_tables.py b/alembic/versions/e825902b2e8a_alter_tables.py deleted file mode 100644 index e40b9060f95dcf8fda270e841991fad6da2e6cc8..0000000000000000000000000000000000000000 --- a/alembic/versions/e825902b2e8a_alter_tables.py +++ /dev/null @@ -1,49 +0,0 @@ -"""alter tables - -Revision ID: e825902b2e8a -Revises: 11d43047d470 -Create Date: 2025-06-02 16:32:45.552509 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = 'e825902b2e8a' -down_revision: Union[str, None] = '11d43047d470' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - op.add_column("rfps", sa.Column("name", sa.String(), nullable=True)) - - op.add_column("proposals", sa.Column("tep", sa.String(), nullable=True)) - - gate_criteria_enum = sa.Enum('PASS', 'FAIL', 'IN_REVIEW', name='gatecriteria') - gate_criteria_enum.create(op.get_bind()) - op.add_column('proposals', sa.Column('gate_criteria', gate_criteria_enum, nullable=False, server_default='IN_REVIEW')) - - op.create_table( - "letters", - sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column("proposal_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("proposals.id", ondelete="CASCADE"), nullable=False), - sa.Column("letter", sa.Text(), nullable=True), - sa.Column("letter_type", sa.Enum("UO", "DEBRIEF", name="lettertype"), nullable=False), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - ) - - -def downgrade() -> None: - """Downgrade schema.""" - op.drop_table("letters") - sa.Enum(name='gatecriteria').drop(op.get_bind(), checkfirst=True) - op.drop_column("proposals", "gate_criteria") - op.drop_column("proposals", "tep") - op.drop_column("rfps", "name") diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index f3941986cb724103c6ba6f763ea7a0c498b484bd..0000000000000000000000000000000000000000 --- a/poetry.lock +++ /dev/null @@ -1,1233 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "aiofiles" -version = "24.1.0" -description = "File support for asyncio." -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, - {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, -] - -[[package]] -name = "aiosqlite" -version = "0.21.0" -description = "asyncio bridge to the standard sqlite3 module" -optional = false -python-versions = ">=3.9" -files = [ - {file = "aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0"}, - {file = "aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3"}, -] - -[package.dependencies] -typing_extensions = ">=4.0" - -[package.extras] -dev = ["attribution (==1.7.1)", "black (==24.3.0)", "build (>=1.2)", "coverage[toml] (==7.6.10)", "flake8 (==7.0.0)", "flake8-bugbear (==24.12.12)", "flit (==3.10.1)", "mypy (==1.14.1)", "ufmt (==2.5.1)", "usort (==1.0.8.post1)"] -docs = ["sphinx (==8.1.3)", "sphinx-mdinclude (==0.6.1)"] - -[[package]] -name = "alembic" -version = "1.16.1" -description = "A database migration tool for SQLAlchemy." -optional = false -python-versions = ">=3.9" -files = [ - {file = "alembic-1.16.1-py3-none-any.whl", hash = "sha256:0cdd48acada30d93aa1035767d67dff25702f8de74d7c3919f2e8492c8db2e67"}, - {file = "alembic-1.16.1.tar.gz", hash = "sha256:43d37ba24b3d17bc1eb1024fe0f51cd1dc95aeb5464594a02c6bb9ca9864bfa4"}, -] - -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.4.0" -typing-extensions = ">=4.12" - -[package.extras] -tz = ["tzdata"] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.9.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.9" -files = [ - {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, - {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "appscript" -version = "1.3.0" -description = "Control AppleScriptable applications from Python." -optional = false -python-versions = "*" -files = [ - {file = "appscript-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c39d9fdbe92dce3c731bce4b818d1e5776d03e8b18706ee25777b7446032a061"}, - {file = "appscript-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a19fdf690e359af634c32d5bec96afd3f8f5229cd2f1c332137ebcbf8fbe90"}, - {file = "appscript-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:76a3507b27c78bf79af83a5f6fac49664b53d530d75632c023e53df1bd350caf"}, - {file = "appscript-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:94ca097d672de5b8cfc82b4179b00cabd21588dbfd939347cf14a9e81955b2d5"}, - {file = "appscript-1.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c0b5c160908de728072d4a0ae57f286608c5d7692bfccbc6eadde868aac2742b"}, - {file = "appscript-1.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2a287b81030c81017127d4fb1c24729623576c50d2ff41694476b9af3ce0a97"}, - {file = "appscript-1.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:13094640e2694b888827d4e133f33dad1e08c9d7102b447c3cc8a73246fdab40"}, - {file = "appscript-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7b4760105810e9b1ecd5b40aba7617e0a047346fb94ee4370e9d37e4383b78d"}, - {file = "appscript-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a48338f9aad0f26f6f1e1d3a90685df771966b2c4f93374f92d7d90d69f3f745"}, - {file = "appscript-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f40d8f82da4ac99ddacc7cf845df56c27a7fa190989fd0c1299eabfeef97add2"}, - {file = "appscript-1.3.0.tar.gz", hash = "sha256:80943118bc97f9f78a8aa55f85565752ed4d82c7893427d7d9ebfdf401c12b2c"}, -] - -[package.dependencies] -lxml = ">=4.7.1" - -[[package]] -name = "asyncpg" -version = "0.30.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e"}, - {file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0"}, - {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f"}, - {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af"}, - {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75"}, - {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f"}, - {file = "asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf"}, - {file = "asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50"}, - {file = "asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a"}, - {file = "asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed"}, - {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a"}, - {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956"}, - {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056"}, - {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454"}, - {file = "asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d"}, - {file = "asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f"}, - {file = "asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e"}, - {file = "asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a"}, - {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3"}, - {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737"}, - {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a"}, - {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af"}, - {file = "asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e"}, - {file = "asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305"}, - {file = "asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70"}, - {file = "asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3"}, - {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33"}, - {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4"}, - {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4"}, - {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba"}, - {file = "asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590"}, - {file = "asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e"}, - {file = "asyncpg-0.30.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29ff1fc8b5bf724273782ff8b4f57b0f8220a1b2324184846b39d1ab4122031d"}, - {file = "asyncpg-0.30.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64e899bce0600871b55368b8483e5e3e7f1860c9482e7f12e0a771e747988168"}, - {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b290f4726a887f75dcd1b3006f484252db37602313f806e9ffc4e5996cfe5cb"}, - {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f86b0e2cd3f1249d6fe6fd6cfe0cd4538ba994e2d8249c0491925629b9104d0f"}, - {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:393af4e3214c8fa4c7b86da6364384c0d1b3298d45803375572f415b6f673f38"}, - {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fd4406d09208d5b4a14db9a9dbb311b6d7aeeab57bded7ed2f8ea41aeef39b34"}, - {file = "asyncpg-0.30.0-cp38-cp38-win32.whl", hash = "sha256:0b448f0150e1c3b96cb0438a0d0aa4871f1472e58de14a3ec320dbb2798fb0d4"}, - {file = "asyncpg-0.30.0-cp38-cp38-win_amd64.whl", hash = "sha256:f23b836dd90bea21104f69547923a02b167d999ce053f3d502081acea2fba15b"}, - {file = "asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad"}, - {file = "asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff"}, - {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708"}, - {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144"}, - {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb"}, - {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547"}, - {file = "asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a"}, - {file = "asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773"}, - {file = "asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851"}, -] - -[package.extras] -docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"] -gssauth = ["gssapi", "sspilib"] -test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"] - -[[package]] -name = "click" -version = "8.2.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "docx2pdf" -version = "0.1.8" -description = "Convert docx to pdf on Windows or macOS directly using Microsoft Word (must be installed)." -optional = false -python-versions = ">=3.5" -files = [ - {file = "docx2pdf-0.1.8-py3-none-any.whl", hash = "sha256:00be1401fd486640314e993423a0a1cbdbc21142186f68549d962d505b2e8a12"}, - {file = "docx2pdf-0.1.8.tar.gz", hash = "sha256:6d2c20f9ad36eec75f4da017dc7a97622946954a6124ca0b11772875fa86fbed"}, -] - -[package.dependencies] -appscript = {version = ">=1.1.0", markers = "sys_platform == \"darwin\""} -pywin32 = {version = ">=227", markers = "sys_platform == \"win32\""} -tqdm = ">=4.41.0" - -[[package]] -name = "et-xmlfile" -version = "2.0.0" -description = "An implementation of lxml.xmlfile for the standard library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, - {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, -] - -[[package]] -name = "fastapi" -version = "0.115.12" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"}, - {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.47.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "greenlet" -version = "3.2.2" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.9" -files = [ - {file = "greenlet-3.2.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c49e9f7c6f625507ed83a7485366b46cbe325717c60837f7244fc99ba16ba9d6"}, - {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3cc1a3ed00ecfea8932477f729a9f616ad7347a5e55d50929efa50a86cb7be7"}, - {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9896249fbef2c615853b890ee854f22c671560226c9221cfd27c995db97e5c"}, - {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7409796591d879425997a518138889d8d17e63ada7c99edc0d7a1c22007d4907"}, - {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7791dcb496ec53d60c7f1c78eaa156c21f402dda38542a00afc3e20cae0f480f"}, - {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8009ae46259e31bc73dc183e402f548e980c96f33a6ef58cc2e7865db012e13"}, - {file = "greenlet-3.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fd9fb7c941280e2c837b603850efc93c999ae58aae2b40765ed682a6907ebbc5"}, - {file = "greenlet-3.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:00cd814b8959b95a546e47e8d589610534cfb71f19802ea8a2ad99d95d702057"}, - {file = "greenlet-3.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:d0cb7d47199001de7658c213419358aa8937df767936506db0db7ce1a71f4a2f"}, - {file = "greenlet-3.2.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:dcb9cebbf3f62cb1e5afacae90761ccce0effb3adaa32339a0670fe7805d8068"}, - {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf3fc9145141250907730886b031681dfcc0de1c158f3cc51c092223c0f381ce"}, - {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efcdfb9df109e8a3b475c016f60438fcd4be68cd13a365d42b35914cdab4bb2b"}, - {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd139e4943547ce3a56ef4b8b1b9479f9e40bb47e72cc906f0f66b9d0d5cab3"}, - {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71566302219b17ca354eb274dfd29b8da3c268e41b646f330e324e3967546a74"}, - {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3091bc45e6b0c73f225374fefa1536cd91b1e987377b12ef5b19129b07d93ebe"}, - {file = "greenlet-3.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:44671c29da26539a5f142257eaba5110f71887c24d40df3ac87f1117df589e0e"}, - {file = "greenlet-3.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c23ea227847c9dbe0b3910f5c0dd95658b607137614eb821e6cbaecd60d81cc6"}, - {file = "greenlet-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:0a16fb934fcabfdfacf21d79e6fed81809d8cd97bc1be9d9c89f0e4567143d7b"}, - {file = "greenlet-3.2.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:df4d1509efd4977e6a844ac96d8be0b9e5aa5d5c77aa27ca9f4d3f92d3fcf330"}, - {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da956d534a6d1b9841f95ad0f18ace637668f680b1339ca4dcfb2c1837880a0b"}, - {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c7b15fb9b88d9ee07e076f5a683027bc3befd5bb5d25954bb633c385d8b737e"}, - {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752f0e79785e11180ebd2e726c8a88109ded3e2301d40abced2543aa5d164275"}, - {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae572c996ae4b5e122331e12bbb971ea49c08cc7c232d1bd43150800a2d6c65"}, - {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02f5972ff02c9cf615357c17ab713737cccfd0eaf69b951084a9fd43f39833d3"}, - {file = "greenlet-3.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4fefc7aa68b34b9224490dfda2e70ccf2131368493add64b4ef2d372955c207e"}, - {file = "greenlet-3.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a31ead8411a027c2c4759113cf2bd473690517494f3d6e4bf67064589afcd3c5"}, - {file = "greenlet-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b24c7844c0a0afc3ccbeb0b807adeefb7eff2b5599229ecedddcfeb0ef333bec"}, - {file = "greenlet-3.2.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3ab7194ee290302ca15449f601036007873028712e92ca15fc76597a0aeb4c59"}, - {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc5c43bb65ec3669452af0ab10729e8fdc17f87a1f2ad7ec65d4aaaefabf6bf"}, - {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:decb0658ec19e5c1f519faa9a160c0fc85a41a7e6654b3ce1b44b939f8bf1325"}, - {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fadd183186db360b61cb34e81117a096bff91c072929cd1b529eb20dd46e6c5"}, - {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1919cbdc1c53ef739c94cf2985056bcc0838c1f217b57647cbf4578576c63825"}, - {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3885f85b61798f4192d544aac7b25a04ece5fe2704670b4ab73c2d2c14ab740d"}, - {file = "greenlet-3.2.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:85f3e248507125bf4af607a26fd6cb8578776197bd4b66e35229cdf5acf1dfbf"}, - {file = "greenlet-3.2.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1e76106b6fc55fa3d6fe1c527f95ee65e324a13b62e243f77b48317346559708"}, - {file = "greenlet-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe46d4f8e94e637634d54477b0cfabcf93c53f29eedcbdeecaf2af32029b4421"}, - {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba30e88607fb6990544d84caf3c706c4b48f629e18853fc6a646f82db9629418"}, - {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:055916fafad3e3388d27dd68517478933a97edc2fc54ae79d3bec827de2c64c4"}, - {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2593283bf81ca37d27d110956b79e8723f9aa50c4bcdc29d3c0543d4743d2763"}, - {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89c69e9a10670eb7a66b8cef6354c24671ba241f46152dd3eed447f79c29fb5b"}, - {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a98600899ca1ca5d3a2590974c9e3ec259503b2d6ba6527605fcd74e08e207"}, - {file = "greenlet-3.2.2-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b50a8c5c162469c3209e5ec92ee4f95c8231b11db6a04db09bbe338176723bb8"}, - {file = "greenlet-3.2.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:45f9f4853fb4cc46783085261c9ec4706628f3b57de3e68bae03e8f8b3c0de51"}, - {file = "greenlet-3.2.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:9ea5231428af34226c05f927e16fc7f6fa5e39e3ad3cd24ffa48ba53a47f4240"}, - {file = "greenlet-3.2.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:1e4747712c4365ef6765708f948acc9c10350719ca0545e362c24ab973017370"}, - {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782743700ab75716650b5238a4759f840bb2dcf7bff56917e9ffdf9f1f23ec59"}, - {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:354f67445f5bed6604e493a06a9a49ad65675d3d03477d38a4db4a427e9aad0e"}, - {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3aeca9848d08ce5eb653cf16e15bb25beeab36e53eb71cc32569f5f3afb2a3aa"}, - {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cb8553ee954536500d88a1a2f58fcb867e45125e600e80f586ade399b3f8819"}, - {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1592a615b598643dbfd566bac8467f06c8c8ab6e56f069e573832ed1d5d528cc"}, - {file = "greenlet-3.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f72667cc341c95184f1c68f957cb2d4fc31eef81646e8e59358a10ce6689457"}, - {file = "greenlet-3.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a8fa80665b1a29faf76800173ff5325095f3e66a78e62999929809907aca5659"}, - {file = "greenlet-3.2.2-cp39-cp39-win32.whl", hash = "sha256:6629311595e3fe7304039c67f00d145cd1d38cf723bb5b99cc987b23c1433d61"}, - {file = "greenlet-3.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:eeb27bece45c0c2a5842ac4c5a1b5c2ceaefe5711078eed4e8043159fa05c834"}, - {file = "greenlet-3.2.2.tar.gz", hash = "sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "loguru" -version = "0.7.3" -description = "Python logging made (stupidly) simple" -optional = false -python-versions = "<4.0,>=3.5" -files = [ - {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, - {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} -win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} - -[package.extras] -dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"] - -[[package]] -name = "lxml" -version = "5.4.0" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -optional = false -python-versions = ">=3.6" -files = [ - {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c"}, - {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c"}, - {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b"}, - {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b"}, - {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563"}, - {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5"}, - {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776"}, - {file = "lxml-5.4.0-cp310-cp310-win32.whl", hash = "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7"}, - {file = "lxml-5.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250"}, - {file = "lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9"}, - {file = "lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8"}, - {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86"}, - {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056"}, - {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7"}, - {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd"}, - {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751"}, - {file = "lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4"}, - {file = "lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539"}, - {file = "lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4"}, - {file = "lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7"}, - {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079"}, - {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20"}, - {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8"}, - {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f"}, - {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc"}, - {file = "lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f"}, - {file = "lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2"}, - {file = "lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0"}, - {file = "lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8"}, - {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982"}, - {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61"}, - {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54"}, - {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b"}, - {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a"}, - {file = "lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82"}, - {file = "lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f"}, - {file = "lxml-5.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7be701c24e7f843e6788353c055d806e8bd8466b52907bafe5d13ec6a6dbaecd"}, - {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb54f7c6bafaa808f27166569b1511fc42701a7713858dddc08afdde9746849e"}, - {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97dac543661e84a284502e0cf8a67b5c711b0ad5fb661d1bd505c02f8cf716d7"}, - {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:c70e93fba207106cb16bf852e421c37bbded92acd5964390aad07cb50d60f5cf"}, - {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9c886b481aefdf818ad44846145f6eaf373a20d200b5ce1a5c8e1bc2d8745410"}, - {file = "lxml-5.4.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:fa0e294046de09acd6146be0ed6727d1f42ded4ce3ea1e9a19c11b6774eea27c"}, - {file = "lxml-5.4.0-cp36-cp36m-win32.whl", hash = "sha256:61c7bbf432f09ee44b1ccaa24896d21075e533cd01477966a5ff5a71d88b2f56"}, - {file = "lxml-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121"}, - {file = "lxml-5.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872"}, - {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e"}, - {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f7f991a68d20c75cb13c5c9142b2a3f9eb161f1f12a9489c82172d1f133c0"}, - {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92"}, - {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:ac7ba71f9561cd7d7b55e1ea5511543c0282e2b6450f122672a2694621d63b7e"}, - {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5"}, - {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:ce31158630a6ac85bddd6b830cffd46085ff90498b397bd0a259f59d27a12188"}, - {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6"}, - {file = "lxml-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063"}, - {file = "lxml-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49"}, - {file = "lxml-5.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eaf24066ad0b30917186420d51e2e3edf4b0e2ea68d8cd885b14dc8afdcf6556"}, - {file = "lxml-5.4.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b31a3a77501d86d8ade128abb01082724c0dfd9524f542f2f07d693c9f1175f"}, - {file = "lxml-5.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e108352e203c7afd0eb91d782582f00a0b16a948d204d4dec8565024fafeea5"}, - {file = "lxml-5.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11a96c3b3f7551c8a8109aa65e8594e551d5a84c76bf950da33d0fb6dfafab7"}, - {file = "lxml-5.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:ca755eebf0d9e62d6cb013f1261e510317a41bf4650f22963474a663fdfe02aa"}, - {file = "lxml-5.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:4cd915c0fb1bed47b5e6d6edd424ac25856252f09120e3e8ba5154b6b921860e"}, - {file = "lxml-5.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:226046e386556a45ebc787871d6d2467b32c37ce76c2680f5c608e25823ffc84"}, - {file = "lxml-5.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b108134b9667bcd71236c5a02aad5ddd073e372fb5d48ea74853e009fe38acb6"}, - {file = "lxml-5.4.0-cp38-cp38-win32.whl", hash = "sha256:1320091caa89805df7dcb9e908add28166113dcd062590668514dbd510798c88"}, - {file = "lxml-5.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:073eb6dcdf1f587d9b88c8c93528b57eccda40209cf9be549d469b942b41d70b"}, - {file = "lxml-5.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bda3ea44c39eb74e2488297bb39d47186ed01342f0022c8ff407c250ac3f498e"}, - {file = "lxml-5.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9ceaf423b50ecfc23ca00b7f50b64baba85fb3fb91c53e2c9d00bc86150c7e40"}, - {file = "lxml-5.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:664cdc733bc87449fe781dbb1f309090966c11cc0c0cd7b84af956a02a8a4729"}, - {file = "lxml-5.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67ed8a40665b84d161bae3181aa2763beea3747f748bca5874b4af4d75998f87"}, - {file = "lxml-5.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b4a3bd174cc9cdaa1afbc4620c049038b441d6ba07629d89a83b408e54c35cd"}, - {file = "lxml-5.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:b0989737a3ba6cf2a16efb857fb0dfa20bc5c542737fddb6d893fde48be45433"}, - {file = "lxml-5.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:dc0af80267edc68adf85f2a5d9be1cdf062f973db6790c1d065e45025fa26140"}, - {file = "lxml-5.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:639978bccb04c42677db43c79bdaa23785dc7f9b83bfd87570da8207872f1ce5"}, - {file = "lxml-5.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a99d86351f9c15e4a901fc56404b485b1462039db59288b203f8c629260a142"}, - {file = "lxml-5.4.0-cp39-cp39-win32.whl", hash = "sha256:3e6d5557989cdc3ebb5302bbdc42b439733a841891762ded9514e74f60319ad6"}, - {file = "lxml-5.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:a8c9b7f16b63e65bbba889acb436a1034a82d34fa09752d754f88d708eca80e1"}, - {file = "lxml-5.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55"}, - {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740"}, - {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5"}, - {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37"}, - {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571"}, - {file = "lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4"}, - {file = "lxml-5.4.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5f11a1526ebd0dee85e7b1e39e39a0cc0d9d03fb527f56d8457f6df48a10dc0c"}, - {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b4afaf38bf79109bb060d9016fad014a9a48fb244e11b94f74ae366a64d252"}, - {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6f6bb8a7840c7bf216fb83eec4e2f79f7325eca8858167b68708b929ab2172"}, - {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5cca36a194a4eb4e2ed6be36923d3cffd03dcdf477515dea687185506583d4c9"}, - {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b7c86884ad23d61b025989d99bfdd92a7351de956e01c61307cb87035960bcb1"}, - {file = "lxml-5.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:53d9469ab5460402c19553b56c3648746774ecd0681b1b27ea74d5d8a3ef5590"}, - {file = "lxml-5.4.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:56dbdbab0551532bb26c19c914848d7251d73edb507c3079d6805fa8bba5b706"}, - {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14479c2ad1cb08b62bb941ba8e0e05938524ee3c3114644df905d2331c76cd57"}, - {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32697d2ea994e0db19c1df9e40275ffe84973e4232b5c274f47e7c1ec9763cdd"}, - {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:24f6df5f24fc3385f622c0c9d63fe34604893bc1a5bdbb2dbf5870f85f9a404a"}, - {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:151d6c40bc9db11e960619d2bf2ec5829f0aaffb10b41dcf6ad2ce0f3c0b2325"}, - {file = "lxml-5.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4025bf2884ac4370a3243c5aa8d66d3cb9e15d3ddd0af2d796eccc5f0244390e"}, - {file = "lxml-5.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9459e6892f59ecea2e2584ee1058f5d8f629446eab52ba2305ae13a32a059530"}, - {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47fb24cc0f052f0576ea382872b3fc7e1f7e3028e53299ea751839418ade92a6"}, - {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50441c9de951a153c698b9b99992e806b71c1f36d14b154592580ff4a9d0d877"}, - {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ab339536aa798b1e17750733663d272038bf28069761d5be57cb4a9b0137b4f8"}, - {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9776af1aad5a4b4a1317242ee2bea51da54b2a7b7b48674be736d463c999f37d"}, - {file = "lxml-5.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:63e7968ff83da2eb6fdda967483a7a023aa497d85ad8f05c3ad9b1f2e8c84987"}, - {file = "lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd"}, -] - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html-clean = ["lxml_html_clean"] -html5 = ["html5lib"] -htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.11,<3.1.0)"] - -[[package]] -name = "mako" -version = "1.3.10" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"}, - {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "markdown2" -version = "2.5.3" -description = "A fast and complete Python implementation of Markdown" -optional = false -python-versions = "<4,>=3.9" -files = [ - {file = "markdown2-2.5.3-py3-none-any.whl", hash = "sha256:a8ebb7e84b8519c37bf7382b3db600f1798a22c245bfd754a1f87ca8d7ea63b3"}, - {file = "markdown2-2.5.3.tar.gz", hash = "sha256:4d502953a4633408b0ab3ec503c5d6984d1b14307e32b325ec7d16ea57524895"}, -] - -[package.extras] -all = ["latex2mathml", "pygments (>=2.7.3)", "wavedrom"] -code-syntax-highlighting = ["pygments (>=2.7.3)"] -latex = ["latex2mathml"] -wavedrom = ["wavedrom"] - -[[package]] -name = "markupsafe" -version = "3.0.2" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] - -[[package]] -name = "numpy" -version = "2.2.6" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.10" -files = [ - {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, - {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, - {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, - {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, - {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, - {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, - {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, - {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, - {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, - {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, - {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, - {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, -] - -[[package]] -name = "openpyxl" -version = "3.1.5" -description = "A Python library to read/write Excel 2010 xlsx/xlsm files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, - {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, -] - -[package.dependencies] -et-xmlfile = "*" - -[[package]] -name = "pandas" -version = "2.3.0" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:625466edd01d43b75b1883a64d859168e4556261a5035b32f9d743b67ef44634"}, - {file = "pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675"}, - {file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2"}, - {file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e"}, - {file = "pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6"}, - {file = "pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2"}, - {file = "pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca"}, - {file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d"}, - {file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46"}, - {file = "pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c"}, - {file = "pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a"}, - {file = "pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf"}, - {file = "pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d"}, - {file = "pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b"}, - {file = "pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be"}, - {file = "pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983"}, - {file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f"}, - {file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3"}, - {file = "pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9"}, - {file = "pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390"}, - {file = "pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575"}, - {file = "pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042"}, - {file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c"}, - {file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67"}, - {file = "pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249"}, - {file = "pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133"}, -] - -[package.dependencies] -numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "pydantic" -version = "2.11.5" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7"}, - {file = "pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.33.2" -typing-extensions = ">=4.12.2" -typing-inspection = ">=0.4.0" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.33.2" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, - {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pymupdf" -version = "1.26.0" -description = "A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pymupdf-1.26.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:86195673a5f2b3fbf0f75c73c40975a893fc42bf3d5c2a7cce9fb160704d9997"}, - {file = "pymupdf-1.26.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:26c5df128a0ed7c38b80a1e7ddd792dd4d8b264839b6b8b6b6b768b13c0bb60e"}, - {file = "pymupdf-1.26.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84a4dc96144a6bdf13f7d78c21500b3d1bef14d658156afd479acb3995f650c3"}, - {file = "pymupdf-1.26.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a3f6a45fcf8177763a2629a2ab2cad326e8950a0d120b174b56369365355a2a7"}, - {file = "pymupdf-1.26.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5d4751696ba888ab1a91c6a0208c5d31724ee0bebe406f7a83067a08214f88b"}, - {file = "pymupdf-1.26.0-cp39-abi3-win32.whl", hash = "sha256:eeb04a439355e2077f7675b8ad9377263d81990fc507748f2254a87d771d682b"}, - {file = "pymupdf-1.26.0-cp39-abi3-win_amd64.whl", hash = "sha256:e39cc74ff030d773c4e76b9e5c5919cc4683895b73bd63bfd7a349a53ab5e8d7"}, - {file = "pymupdf-1.26.0.tar.gz", hash = "sha256:ffe023f820379c84a0ddae38b0d07ea4016e1de84929491c34415520c629bcce"}, -] - -[[package]] -name = "pypdf2" -version = "3.0.1" -description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyPDF2-3.0.1.tar.gz", hash = "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440"}, - {file = "pypdf2-3.0.1-py3-none-any.whl", hash = "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928"}, -] - -[package.extras] -crypto = ["PyCryptodome"] -dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "wheel"] -docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] -full = ["Pillow", "PyCryptodome"] -image = ["Pillow"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-docx" -version = "1.1.2" -description = "Create, read, and update Microsoft Word .docx files." -optional = false -python-versions = ">=3.7" -files = [ - {file = "python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe"}, - {file = "python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd"}, -] - -[package.dependencies] -lxml = ">=3.1.0" -typing-extensions = ">=4.9.0" - -[[package]] -name = "python-dotenv" -version = "1.1.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.9" -files = [ - {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, - {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-multipart" -version = "0.0.20" -description = "A streaming multipart parser for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, - {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, -] - -[[package]] -name = "pytz" -version = "2025.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, - {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, -] - -[[package]] -name = "pywin32" -version = "310" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, - {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, - {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, - {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, - {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, - {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, - {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, - {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, - {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, - {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, - {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, - {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, - {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, - {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, - {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, - {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.41" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.41-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6854175807af57bdb6425e47adbce7d20a4d79bbfd6f6d6519cd10bb7109a7f8"}, - {file = "SQLAlchemy-2.0.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05132c906066142103b83d9c250b60508af556982a385d96c4eaa9fb9720ac2b"}, - {file = "SQLAlchemy-2.0.41-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4af17bda11e907c51d10686eda89049f9ce5669b08fbe71a29747f1e876036"}, - {file = "SQLAlchemy-2.0.41-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c0b0e5e1b5d9f3586601048dd68f392dc0cc99a59bb5faf18aab057ce00d00b2"}, - {file = "SQLAlchemy-2.0.41-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0b3dbf1e7e9bc95f4bac5e2fb6d3fb2f083254c3fdd20a1789af965caf2d2348"}, - {file = "SQLAlchemy-2.0.41-cp37-cp37m-win32.whl", hash = "sha256:1e3f196a0c59b0cae9a0cd332eb1a4bda4696e863f4f1cf84ab0347992c548c2"}, - {file = "SQLAlchemy-2.0.41-cp37-cp37m-win_amd64.whl", hash = "sha256:6ab60a5089a8f02009f127806f777fca82581c49e127f08413a66056bd9166dd"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-win32.whl", hash = "sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda"}, - {file = "sqlalchemy-2.0.41-cp310-cp310-win_amd64.whl", hash = "sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8"}, - {file = "sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6"}, - {file = "sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f"}, - {file = "sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90144d3b0c8b139408da50196c5cad2a6909b51b23df1f0538411cd23ffa45d3"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:023b3ee6169969beea3bb72312e44d8b7c27c75b347942d943cf49397b7edeb5"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725875a63abf7c399d4548e686debb65cdc2549e1825437096a0af1f7e374814"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81965cc20848ab06583506ef54e37cf15c83c7e619df2ad16807c03100745dea"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dd5ec3aa6ae6e4d5b5de9357d2133c07be1aff6405b136dad753a16afb6717dd"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ff8e80c4c4932c10493ff97028decfdb622de69cae87e0f127a7ebe32b4069c6"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-win32.whl", hash = "sha256:4d44522480e0bf34c3d63167b8cfa7289c1c54264c2950cc5fc26e7850967e45"}, - {file = "sqlalchemy-2.0.41-cp38-cp38-win_amd64.whl", hash = "sha256:81eedafa609917040d39aa9332e25881a8e7a0862495fcdf2023a9667209deda"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9a420a91913092d1e20c86a2f5f1fc85c1a8924dbcaf5e0586df8aceb09c9cc2"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:906e6b0d7d452e9a98e5ab8507c0da791856b2380fdee61b765632bb8698026f"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a373a400f3e9bac95ba2a06372c4fd1412a7cee53c37fc6c05f829bf672b8769"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:087b6b52de812741c27231b5a3586384d60c353fbd0e2f81405a814b5591dc8b"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:34ea30ab3ec98355235972dadc497bb659cc75f8292b760394824fab9cf39826"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8280856dd7c6a68ab3a164b4a4b1c51f7691f6d04af4d4ca23d6ecf2261b7923"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-win32.whl", hash = "sha256:b50eab9994d64f4a823ff99a0ed28a6903224ddbe7fef56a6dd865eec9243440"}, - {file = "sqlalchemy-2.0.41-cp39-cp39-win_amd64.whl", hash = "sha256:5e22575d169529ac3e0a120cf050ec9daa94b6a9597993d1702884f6954a7d71"}, - {file = "sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576"}, - {file = "sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9"}, -] - -[package.dependencies] -greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] -aioodbc = ["aioodbc", "greenlet (>=1)"] -aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (>=1)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "starlette" -version = "0.46.2" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.9" -files = [ - {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, - {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, -] - -[package.dependencies] -anyio = ">=3.6.2,<5" - -[package.extras] -full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] - -[[package]] -name = "tqdm" -version = "4.67.1" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, - {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] -discord = ["requests"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.1" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, - {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[[package]] -name = "tzdata" -version = "2025.2" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, - {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, -] - -[[package]] -name = "uvicorn" -version = "0.34.2" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.9" -files = [ - {file = "uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403"}, - {file = "uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328"}, -] - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "win32-setctime" -version = "1.2.0" -description = "A small Python utility to set file creation time on Windows" -optional = false -python-versions = ">=3.5" -files = [ - {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, - {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, -] - -[package.extras] -dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.12" -content-hash = "467da4167800d5ca73f2f08446f76357d68ba4cd45b5ca1c01b249a29c1e1ca1" diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 8f35c0598ccde59e8b09a1d580243661a98fe0cf..0000000000000000000000000000000000000000 --- a/pyproject.toml +++ /dev/null @@ -1,32 +0,0 @@ -[tool.poetry] -name = "proposal-evaluation" -version = "0.1.0" -description = "" -authors = ["Aryan Jain <77013565+aryanjain1908@users.noreply.github.com>"] -readme = "README.md" - -[tool.poetry.dependencies] -python = "^3.12" -uvicorn = "^0.34.2" -fastapi = "^0.115.12" -python-dotenv = "^1.1.0" -alembic = "^1.16.1" -asyncpg = "^0.30.0" -loguru = "^0.7.3" -pydantic = "^2.11.5" -aiosqlite = "^0.21.0" -python-multipart = "^0.0.20" -pypdf2 = "^3.0.1" -pymupdf = "^1.26.0" -python-docx = "^1.1.2" -docx2pdf = "^0.1.8" -openpyxl = "^3.1.5" -pandas = "^2.3.0" -numpy = "^2.2.6" -aiofiles = "^24.1.0" -markdown2 = "^2.5.3" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 725099dfbfd5a8ce9333221b684e57a726054f2a..0000000000000000000000000000000000000000 --- a/src/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from dotenv import load_dotenv -import os - -from .app import app -from .config import logger - -load_dotenv() - -if not os.getenv("SQLALCHEMY_DATABASE_URI"): - logger.error("SQLALCHEMY_DATABASE_URI is not set") - raise ValueError("SQLALCHEMY_DATABASE_URI is not set") - -__all__ = ["app"] -__version__ = "0.1.0" -__author__ = "Aryan Jain" \ No newline at end of file diff --git a/src/app.py b/src/app.py deleted file mode 100644 index aac16bbf6056c3a21c8d3000ec1105a353a0a1e6..0000000000000000000000000000000000000000 --- a/src/app.py +++ /dev/null @@ -1,65 +0,0 @@ -import asyncio -import os -from contextlib import asynccontextmanager -from threading import Thread - -from alembic import command -from alembic.config import Config -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - -from src.config import DatabaseConfig, logger -from src.controllers import api_router - - - -def run_upgrade(connection, alembic_config: Config): - alembic_config.attributes["connection"] = connection - command.upgrade(alembic_config, "head") - - -async def run_migrations(): - logger.info("Running migrations if any...") - alembic_config = Config("alembic.ini") - alembic_config.set_main_option( - "sqlalchemy.url", os.getenv("SQLALCHEMY_DATABASE_URI") - ) - async with DatabaseConfig.get_engine().begin() as session: - await session.run_sync(run_upgrade, alembic_config) - - -@asynccontextmanager -async def lifespan(app: FastAPI): - try: - logger.info("Starting up the application...") - await run_migrations() - logger.info("Application started successfully...") - yield - except Exception as e: - logger.error(f"Error during startup: {str(e)}") - raise - finally: - logger.info("Shutting down the application...") - logger.info("Application shutdown complete.") - - -app = FastAPI(lifespan=lifespan) - - -app.add_middleware( - CORSMiddleware, - allow_origins=os.getenv( - "CORS_ALLOW_ORIGINS", "http://localhost, http://127.0.0.1" - ).split(", "), - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -@app.get("/") -async def check_health(): - return {"response": "Service is healthy!"} - - -app.include_router(api_router, prefix="/api/v1") \ No newline at end of file diff --git a/src/config/__init__.py b/src/config/__init__.py deleted file mode 100644 index 5c52764b56d1980fe62b4093b397134919ac52a9..0000000000000000000000000000000000000000 --- a/src/config/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._databse_config import DatabaseConfig -from ._logger import logger - -__all__ = [ - "DatabaseConfig", - "logger", -] -__version__ = "0.1.0" -__author__ = "Aryan Jain" \ No newline at end of file diff --git a/src/config/_databse_config.py b/src/config/_databse_config.py deleted file mode 100644 index 3780ab891b0d21da1c7fb0d4c5abc9934d29cb94..0000000000000000000000000000000000000000 --- a/src/config/_databse_config.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import threading -from contextlib import asynccontextmanager - -from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine - - -class DatabaseConfig: - _thread_local = threading.local() - - @classmethod - def get_engine(cls): - if not hasattr(cls._thread_local, "engine"): - cls._thread_local.engine = create_async_engine( - url=os.getenv("SQLALCHEMY_DATABASE_URI"), - pool_pre_ping=True, - pool_recycle=3600, - ) - return cls._thread_local.engine - - @classmethod - def _get_session_factory(cls): - if not hasattr(cls._thread_local, "session_factory"): - cls._thread_local.session_factory = async_sessionmaker( - bind=cls.get_engine(), - autoflush=False, - autocommit=False, - expire_on_commit=False, - ) - return cls._thread_local.session_factory - - @classmethod - @asynccontextmanager - async def async_session(cls): - session_factory = cls._get_session_factory() - async with session_factory() as session: - yield session diff --git a/src/config/_logger.py b/src/config/_logger.py deleted file mode 100644 index 64be710a94d8e89248ce3aa91e11e1281710c7a1..0000000000000000000000000000000000000000 --- a/src/config/_logger.py +++ /dev/null @@ -1,8 +0,0 @@ -import os - -from loguru import logger - -LOG_FILE = os.getenv("LOG_FILE") -if LOG_FILE: - LOG_RETENTION = os.getenv("LOG_RETENTION", "90 days") - logger.add(LOG_FILE, retention=LOG_RETENTION) diff --git a/src/controllers/__init__.py b/src/controllers/__init__.py deleted file mode 100644 index 7ac84550c883f0d0dfeba9cc34c1f94a014c86d1..0000000000000000000000000000000000000000 --- a/src/controllers/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -from fastapi import APIRouter -from ._rfp_controller import RFPController -from ._proposal_controller import ProposalController -from ._proposal_ai_analysis_controller import ProposalAIController -from ._proposal_detailed_analysis_controller import ProposalDetailedController -from ._letter_controller import LetterController -from ._evaluation_controller import EvaluationController -from ._comparative_weight_controller import ComparativeWeightController -from ._proposal_evaluation_controller import ProposalEvaluationController -from ._analysis_conteoller import AnalysisController -from ._file_controller import FileController - -api_router = APIRouter() - -api_router.include_router(RFPController().router) -api_router.include_router(ProposalController().router) -# api_router.include_router(ProposalAIController().router) -# api_router.include_router(ProposalDetailedController().router) -api_router.include_router(LetterController().router) -api_router.include_router(EvaluationController().router) -api_router.include_router(ComparativeWeightController().router) -api_router.include_router(ProposalEvaluationController().router) -api_router.include_router(AnalysisController().router) -api_router.include_router(FileController().router) - -__all__ = ["api_router"] -__version__ = "0.1.0" -__author__ = "Aryan Jain" diff --git a/src/controllers/_analysis_conteoller.py b/src/controllers/_analysis_conteoller.py deleted file mode 100644 index 1d3d75b9b168f90f2c4871447a909acde63182a6..0000000000000000000000000000000000000000 --- a/src/controllers/_analysis_conteoller.py +++ /dev/null @@ -1,249 +0,0 @@ -from fastapi import APIRouter, Body, HTTPException, Query, Path -from pydantic import BaseModel -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger -from src.models import AnalysisType, AnalysisStatus -from src.services import AnalysisService - - -class Analysis(BaseModel): - id: UUID - evaluation_id: UUID - insights: str - analysis_type: AnalysisType - status: AnalysisStatus - created_at: datetime - updated_at: datetime - - -class Response(BaseModel): - status: str - data: Optional[List[Analysis]] = None - - -class CreateAnalysis(BaseModel): - insights: str - analysis_type: AnalysisType - status: Optional[AnalysisStatus] = None - - -class CreateAnalysisRequest(BaseModel): - data: List[CreateAnalysis] - analysis_score_type: Optional[str] = None - - -class UpdateAnalysis(BaseModel): - insights: Optional[str] = None - analysis_type: Optional[AnalysisType] = None - status: Optional[AnalysisStatus] = None - - -class Deleteresponse(BaseModel): - status: str - - -class AnalysisController: - def __init__(self): - self.service = AnalysisService - self.router = APIRouter() - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis", - self.get_analysis_by_proposal_and_rfp_id_and_evaluation_id, - methods=["GET"], - response_model=Response, - tags=["Analysis by Proposal and RFP ID and Evaluation ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}", - self.get_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id, - methods=["GET"], - response_model=Response, - tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"], - ) - - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis", - self.create_analysis_by_proposal_and_rfp_id_and_evaluation_id, - methods=["POST"], - response_model=Response, - tags=["Analysis by Proposal and RFP ID and Evaluation ID"], - ) - - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis", - self.update_analysis_by_proposal_and_rfp_id_and_evaluation_id, - methods=["PUT"], - response_model=Response, - tags=["Analysis by Proposal and RFP ID and Evaluation ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}", - self.update_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id, - methods=["PUT"], - response_model=Response, - tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"], - ) - - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis", - self.delete_analysis_by_proposal_and_rfp_id_and_evaluation_id, - methods=["DELETE"], - response_model=Deleteresponse, - tags=["Analysis by Proposal and RFP ID and Evaluation ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}", - self.delete_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id, - methods=["DELETE"], - response_model=Deleteresponse, - tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"], - ) - - async def get_analysis_by_proposal_and_rfp_id_and_evaluation_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_id: str = Path(...), - analysis_type: AnalysisType = Query(None), - ): - try: - async with self.service() as service: - results = await service.get_analysis( - evaluation_id=evaluation_id, analysis_type=analysis_type - ) - return Response( - status="success", - data=[Analysis(**result) for result in results], - ) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) - - async def get_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_id: str = Path(...), - id: str = Path(...), - ): - try: - async with self.service() as service: - results = await service.get_analysis(evaluation_id=evaluation_id, id=id) - return Response( - status="success", - data=[Analysis(**result) for result in results], - ) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) - - async def create_analysis_by_proposal_and_rfp_id_and_evaluation_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_id: str = Path(...), - analysis: CreateAnalysisRequest = Body(...), - ): - try: - async with self.service() as service: - evaluation_analysis = analysis.model_dump( - mode="json", exclude_unset=True - ) - results = await service.create_analysis( - evaluation_id=evaluation_id, - evaluation_analysis=evaluation_analysis["data"], - analysis_score_type=( - evaluation_analysis["analysis_score_type"] - if "analysis_score_type" in evaluation_analysis - else None - ), - ) - return Response( - status="success", - data=[Analysis(**result) for result in results], - ) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) - - async def update_analysis_by_proposal_and_rfp_id_and_evaluation_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_id: str = Path(...), - analysis: CreateAnalysisRequest = Body(...), - ): - try: - async with self.service() as service: - results = await service.update_analysis( - evaluation_id=evaluation_id, - proposal_analysis=analysis.model_dump( - mode="json", exclude_unset=True - )["data"], - ) - return Response( - status="success", - data=[Analysis(**result) for result in results], - ) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) - - async def update_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_id: str = Path(...), - id: str = Path(...), - analysis: UpdateAnalysis = Body(...), - ): - try: - async with self.service() as service: - results = await service.update_analysis( - evaluation_id=evaluation_id, - id=id, - proposal_analysis=analysis.model_dump( - mode="json", exclude_unset=True - ), - ) - return Response( - status="success", - data=[Analysis(**result) for result in results], - ) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) - - async def delete_analysis_by_proposal_and_rfp_id_and_evaluation_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_id: str = Path(...), - ): - try: - async with self.service() as service: - results = await service.delete_analysis(evaluation_id=evaluation_id) - return Deleteresponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) - - async def delete_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_id: str = Path(...), - id: str = Path(...), - ): - try: - async with self.service() as service: - results = await service.delete_analysis( - evaluation_id=evaluation_id, id=id - ) - return Deleteresponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) diff --git a/src/controllers/_comparative_weight_controller.py b/src/controllers/_comparative_weight_controller.py deleted file mode 100644 index 2a8a2cf38a481b8efe16eeb4a9fc8bf42420d848..0000000000000000000000000000000000000000 --- a/src/controllers/_comparative_weight_controller.py +++ /dev/null @@ -1,237 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path -from pydantic import BaseModel -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger -from src.models import Weights -from src.services import ComparativeWeightService - - -class ComparitiveWeight(BaseModel): - id: UUID - rfp_id: UUID - technical_weight: Weights - management_weight: Weights - past_performance_weight: Weights - price_weight: Weights - strengths_weight: float - weaknesses_weight: float - flag: Optional[bool] = False - created_at: datetime - updated_at: datetime - - -class ResponseComparativeWeight(BaseModel): - status: str - data: Optional[List[ComparitiveWeight]] - - -class DeleteResponse(BaseModel): - status: str - - -class ComparativeWeightRequest(BaseModel): - technical_weight: Optional[Weights] = None - management_weight: Optional[Weights] = None - past_performance_weight: Optional[Weights] = None - price_weight: Optional[Weights] = None - strengths_weight: Optional[float] = None - weaknesses_weight: Optional[float] = None - flag: Optional[bool] = None - - -class ComparativeWeightController: - def __init__(self): - self.comparative_weight_service = ComparativeWeightService - self.router = APIRouter() - self.router.add_api_route( - "/rfps/{rfp_id}/comparative-weights", - self.get_comparative_weights_by_rfp_id, - methods=["GET"], - response_model=ResponseComparativeWeight, - tags=["Comparative Weights by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/comparative-weights/{id}", - self.get_comparative_weights_by_id, - methods=["GET"], - response_model=ResponseComparativeWeight, - tags=["Comparative Weights by RFP ID and ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/comparative-weights", - self.create_comparative_weights, - methods=["POST"], - response_model=ResponseComparativeWeight, - tags=["Comparative Weights by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/comparative-weights/{id}", - self.update_comparative_weights_by_id, - methods=["PUT"], - response_model=ResponseComparativeWeight, - tags=["Comparative Weights by RFP ID and ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/comparative-weights", - self.update_comparative_weights, - methods=["PUT"], - response_model=ResponseComparativeWeight, - tags=["Comparative Weights by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/comparative-weights", - self.delete_comparative_weights, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Comparative Weights by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/comparative-weights/{id}", - self.delete_comparative_weights_by_id, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Comparative Weights by RFP ID and ID"], - ) - - async def get_comparative_weights_by_rfp_id(self, rfp_id: str = Path(...)): - try: - async with self.comparative_weight_service() as service: - comparative_weights = await service.get_comparative_weights( - rfp_id=rfp_id - ) - return ResponseComparativeWeight( - status="success", - data=[ - ComparitiveWeight(**comparative_weight) - for comparative_weight in comparative_weights - ], - ) - except Exception as e: - logger.error(f"Error getting comparative weights by rfp id: {e}") - raise HTTPException( - status_code=500, detail="Error getting comparative weights by rfp id" - ) - - async def get_comparative_weights_by_id( - self, rfp_id: str = Path(...), id: str = Path(...) - ): - try: - async with self.comparative_weight_service() as service: - comparative_weights = await service.get_comparative_weights( - rfp_id=rfp_id, id=id - ) - return ResponseComparativeWeight( - status="success", - data=[ - ComparitiveWeight(**comparative_weight) - for comparative_weight in comparative_weights - ], - ) - except Exception as e: - logger.error(f"Error getting comparative weights by id: {e}") - raise HTTPException( - status_code=500, detail="Error getting comparative weights by id" - ) - - async def create_comparative_weights( - self, comparative_weights: ComparativeWeightRequest, rfp_id: str = Path(...) - ): - try: - async with self.comparative_weight_service() as service: - comparative_weights = await service.create_comparative_weights( - rfp_id=rfp_id, - comparative_weights=comparative_weights.model_dump( - mode="json", exclude_unset=True - ), - ) - return ResponseComparativeWeight( - status="success", - data=[ - ComparitiveWeight(**comparative_weight) - for comparative_weight in comparative_weights - ], - ) - except Exception as e: - logger.error(f"Error creating comparative weights: {e}") - raise HTTPException( - status_code=500, detail="Error creating comparative weights" - ) - - async def update_comparative_weights( - self, comparative_weights: ComparativeWeightRequest, rfp_id: str = Path(...) - ): - try: - async with self.comparative_weight_service() as service: - comparative_weights = await service.update_comparative_weights( - rfp_id=rfp_id, - comparative_weights=comparative_weights.model_dump( - mode="json", exclude_unset=True - ), - ) - return ResponseComparativeWeight( - status="success", - data=[ - ComparitiveWeight(**comparative_weight) - for comparative_weight in comparative_weights - ], - ) - except Exception as e: - logger.error(f"Error updating comparative weights: {e}") - raise HTTPException( - status_code=500, detail="Error updating comparative weights" - ) - - async def update_comparative_weights_by_id( - self, - comparative_weights: ComparativeWeightRequest, - rfp_id: str = Path(...), - id: str = Path(...), - ): - try: - async with self.comparative_weight_service() as service: - comparative_weights = await service.update_comparative_weights( - rfp_id=rfp_id, - id=id, - comparative_weights=comparative_weights.model_dump( - mode="json", exclude_unset=True - ), - ) - return ResponseComparativeWeight( - status="success", - data=[ - ComparitiveWeight(**comparative_weight) - for comparative_weight in comparative_weights - ], - ) - except Exception as e: - logger.error(f"Error updating comparative weights by id: {e}") - raise HTTPException( - status_code=500, detail="Error updating comparative weights by id" - ) - - async def delete_comparative_weights(self, rfp_id: str = Path(...)): - try: - async with self.comparative_weight_service() as service: - await service.delete_comparative_weights(rfp_id=rfp_id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(f"Error deleting comparative weights: {e}") - raise HTTPException( - status_code=500, detail="Error deleting comparative weights" - ) - - async def delete_comparative_weights_by_id( - self, rfp_id: str = Path(...), id: str = Path(...) - ): - try: - async with self.comparative_weight_service() as service: - await service.delete_comparative_weights(rfp_id=rfp_id, id=id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(f"Error deleting comparative weights: {e}") - raise HTTPException( - status_code=500, detail="Error deleting comparative weights" - ) diff --git a/src/controllers/_evaluation_controller.py b/src/controllers/_evaluation_controller.py deleted file mode 100644 index 90dc99f94528fa817dc7a79ace7cfe5b715fe334..0000000000000000000000000000000000000000 --- a/src/controllers/_evaluation_controller.py +++ /dev/null @@ -1,299 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path -from pydantic import BaseModel -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger -from src.services import EvaluationService -from src.models import EvaluationCriteriaType - - -class EvaluationCriteria(BaseModel): - id: UUID - rfp_id: UUID - evaluation_criteria: str - evaluation_criteria_type: EvaluationCriteriaType - created_at: datetime - updated_at: datetime - - -class EvaluationResponse(BaseModel): - status: str - data: List[EvaluationCriteria] - - -class EvaluationRequest(BaseModel): - rfp_id: UUID - evaluation_criteria: str - evaluation_criteria_type: EvaluationCriteriaType - - -class CreateEvaluationRequest(BaseModel): - evaluation_criteria: str - evaluation_criteria_type: EvaluationCriteriaType - - -class UpdateEvaluationRequest(BaseModel): - rfp_id: Optional[UUID] = None - evaluation_criteria: Optional[str] = None - evaluation_criteria_type: Optional[EvaluationCriteriaType] = None - - -class UpdateEvaluationCriteriaByRFPRequest(BaseModel): - evaluation_criteria: Optional[str] = None - evaluation_criteria_type: Optional[EvaluationCriteriaType] = None - - -class DeleteResponse(BaseModel): - status: str - - -class EvaluationController: - def __init__(self): - self.evaluation_service = EvaluationService - self.router = APIRouter() - # self.router.add_api_route( - # "/evaluation-criteria", - # self.get_criteria, - # methods=["GET"], - # response_model=EvaluationResponse, - # tags=["Evaluation Criteria"], - # ) - # self.router.add_api_route( - # "/evaluation-criteria/{id}", - # self.get_criteria_by_id, - # methods=["GET"], - # response_model=EvaluationResponse, - # tags=["Evaluation Criteria by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/evaluation-criteria", - self.get_criteria_by_rfp_id, - methods=["GET"], - response_model=EvaluationResponse, - tags=["Evaluation Criteria by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/evaluation-criteria/{id}", - self.get_criteria_by_proposal_and_rfp_id, - methods=["GET"], - response_model=EvaluationResponse, - tags=["Evaluation Criteria by Proposal and RFP ID"], - ) - # self.router.add_api_route( - # "/evaluation-criteria", - # self.create_criteria, - # methods=["POST"], - # response_model=EvaluationResponse, - # tags=["Evaluation Criteria"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/evaluation-criteria", - self.create_criteria_by_rfp_id, - methods=["POST"], - response_model=EvaluationResponse, - tags=["Evaluation Criteria by RFP ID"], - ) - # self.router.add_api_route( - # "/evaluation-criteria/{id}", - # self.update_criteria, - # methods=["PUT"], - # response_model=EvaluationResponse, - # tags=["Evaluation Criteria by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/evaluation-criteria/{id}", - self.update_criteria_by_proposal_and_rfp_id, - methods=["PUT"], - response_model=EvaluationResponse, - tags=["Evaluation Criteria by Proposal and RFP ID"], - ) - # self.router.add_api_route( - # "/evaluation-criteria/{id}", - # self.delete_criteria, - # methods=["DELETE"], - # response_model=DeleteResponse, - # tags=["Evaluation Criteria by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/evaluation-criteria", - self.delete_criteria_by_rfp_id, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Evaluation Criteria by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/evaluation-criteria/{id}", - self.delete_criteria_by_proposal_and_rfp_id, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Evaluation Criteria by Proposal and RFP ID"], - ) - - async def get_criteria(self): - try: - async with self.evaluation_service() as service: - result = await service.get_criteria() - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error fetching evaluation criteria" - ) - - async def get_criteria_by_id(self, id: str = Path(...)): - try: - async with self.evaluation_service() as service: - result = await service.get_criteria(id=id) - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error fetching evaluation criteria" - ) - - async def get_criteria_by_rfp_id( - self, - rfp_id: str = Path(...), - criteria_type: Optional[EvaluationCriteriaType] = Query(None), - ): - try: - async with self.evaluation_service() as service: - result = await service.get_criteria( - rfp_id=rfp_id, evaluation_criteria_type=criteria_type - ) - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error fetching evaluation criteria" - ) - - async def get_criteria_by_proposal_and_rfp_id( - self, rfp_id: str = Path(...), id: str = Path(...) - ): - try: - async with self.evaluation_service() as service: - result = await service.get_criteria(rfp_id=rfp_id, id=id) - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error fetching evaluation criteria" - ) - - async def create_criteria(self, criteria: EvaluationRequest): - try: - async with self.evaluation_service() as service: - result = await service.create_criteria( - criteria.model_dump(exclude_unset=True, mode="json") - ) - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error creating evaluation criteria" - ) - - async def create_criteria_by_rfp_id( - self, criteria: CreateEvaluationRequest, rfp_id: str = Path(...) - ): - try: - async with self.evaluation_service() as service: - data = criteria.model_dump(exclude_unset=True, mode="json") - data["rfp_id"] = rfp_id - result = await service.create_criteria(evaluation_criteria=data) - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error creating evaluation criteria" - ) - - async def update_criteria( - self, criteria: UpdateEvaluationRequest, id: str = Path(...) - ): - try: - async with self.evaluation_service() as service: - result = await service.update_criteria( - id=id, - evaluation_criteria=criteria.model_dump( - exclude_unset=True, mode="json" - ), - ) - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error updating evaluation criteria" - ) - - async def update_criteria_by_proposal_and_rfp_id( - self, - criteria: UpdateEvaluationCriteriaByRFPRequest, - rfp_id: str = Path(...), - id: str = Path(...), - ): - try: - async with self.evaluation_service() as service: - data = criteria.model_dump(exclude_unset=True, mode="json") - data["rfp_id"] = rfp_id - result = await service.update_criteria(id=id, evaluation_criteria=data) - return EvaluationResponse( - status="success", data=[EvaluationCriteria(**r) for r in result] - ) - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error updating evaluation criteria" - ) - - async def delete_criteria(self, id: str = Path(...)): - try: - async with self.evaluation_service() as service: - await service.delete_criteria(id=id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error deleting evaluation criteria" - ) - - async def delete_criteria_by_rfp_id(self, rfp_id: str = Path(...)): - try: - async with self.evaluation_service() as service: - await service.delete_criteria(rfp_id=rfp_id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error deleting evaluation criteria" - ) - - async def delete_criteria_by_proposal_and_rfp_id( - self, rfp_id: str = Path(...), id: str = Path(...) - ): - try: - async with self.evaluation_service() as service: - await service.delete_criteria(rfp_id=rfp_id, id=id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException( - status_code=500, detail="Error deleting evaluation criteria" - ) diff --git a/src/controllers/_file_controller.py b/src/controllers/_file_controller.py deleted file mode 100644 index 68aec58597b29e427deca73125873dbcf36ac5f0..0000000000000000000000000000000000000000 --- a/src/controllers/_file_controller.py +++ /dev/null @@ -1,34 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path, UploadFile, File -from pydantic import BaseModel -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger -from src.services import FileService - -import io - -class Response(BaseModel): - status: str - data: str - -class FileController: - def __init__(self): - self.service = FileService - self.router = APIRouter() - self.router.add_api_route( - "/files", - self.get_files_extrcated_text, - methods=["POST"], - tags=["Files"], - ) - - async def get_files_extrcated_text(self, file: UploadFile = File(...)): - try: - async with self.service() as service: - result = await service.extract_file(file=file) - return result - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error processing file") \ No newline at end of file diff --git a/src/controllers/_letter_controller.py b/src/controllers/_letter_controller.py deleted file mode 100644 index ed0455b6d9e77730fb44c10ac27311c14415d832..0000000000000000000000000000000000000000 --- a/src/controllers/_letter_controller.py +++ /dev/null @@ -1,263 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path -from pydantic import BaseModel -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger -from src.services import LetterService -from src.models import LetterType - - -class Letter(BaseModel): - id: UUID - proposal_id: UUID - letter: str - letter_type: LetterType - created_at: datetime - updated_at: datetime - - -class Response(BaseModel): - status: str - data: List[Letter] - - -class LetterRequest(BaseModel): - proposal_id: UUID - letter: str - letter_type: LetterType - - -class CreateLetterRequest(BaseModel): - letter: str - letter_type: LetterType - - -class UpdateLetterRequest(BaseModel): - proposal_id: Optional[UUID] = None - letter: Optional[str] = None - letter_type: Optional[LetterType] = None - - -class UpdateLetterRequestByProposalAndId(BaseModel): - letter: Optional[str] = None - letter_type: Optional[LetterType] = None - - -class DeleteResponse(BaseModel): - status: str - - -class LetterController: - def __init__(self): - self.letter_service = LetterService - self.router = APIRouter() - # self.router.add_api_route( - # "/letters", - # self.get_letters, - # methods=["GET"], - # response_model=Response, - # tags=["Letters"], - # ) - # self.router.add_api_route( - # "/letters/{id}", - # self.get_letters_by_id, - # methods=["GET"], - # response_model=Response, - # tags=["Letters by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/letters", - self.get_letters_by_proposal_id, - methods=["GET"], - response_model=Response, - tags=["Letters by Proposal ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/letters/{id}", - self.get_letters_by_proposal_and_id, - methods=["GET"], - response_model=Response, - tags=["Letters by Proposal and ID"], - ) - # self.router.add_api_route( - # "/letters", - # self.create_letter, - # methods=["POST"], - # response_model=Response, - # tags=["Letters"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/letters", - self.create_letters_by_proposal_id, - methods=["POST"], - response_model=Response, - tags=["Letters by Proposal ID"], - ) - # self.router.add_api_route( - # "/letters/{id}", - # self.update_letter, - # methods=["PUT"], - # response_model=Response, - # tags=["Letters by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/letters/{id}", - self.update_letters_by_proposal_and_id, - methods=["PUT"], - response_model=Response, - tags=["Letters by Proposal and ID"], - ) - # self.router.add_api_route( - # "/letters/{id}", - # self.delete_letter, - # methods=["DELETE"], - # response_model=DeleteResponse, - # tags=["Letters by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/letters", - self.delete_letters_by_proposal_id, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Letters by Proposal ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/letters/{id}", - self.delete_letters_by_proposal_and_id, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Letters by Proposal and ID"], - ) - - async def get_letters(self): - try: - async with self.letter_service() as service: - result = await service.get_letters() - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error fetching letters") - - async def get_letters_by_id(self, id: str = Path(...)): - try: - async with self.letter_service() as service: - result = await service.get_letters(id=id) - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error fetching letter") - - async def get_letters_by_proposal_id( - self, - proposal_id: str = Path(...), - letter_type: LetterType = Query(None), - rfp_id: str = Path(...), - ): - try: - async with self.letter_service() as service: - result = await service.get_letters( - proposal_id=proposal_id, letter_type=letter_type - ) - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error fetching letter") - - async def get_letters_by_proposal_and_id( - self, proposal_id: str = Path(...), id: str = Path(...), rfp_id: str = Path(...) - ): - try: - async with self.letter_service() as service: - result = await service.get_letters(proposal_id=proposal_id, id=id) - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error fetching letter") - - async def create_letter(self, letter: LetterRequest): - try: - async with self.letter_service() as service: - result = await service.create_letter( - letter.model_dump(exclude_unset=True, mode="json") - ) - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error creating letter") - - async def create_letters_by_proposal_id( - self, - letter: CreateLetterRequest, - proposal_id: str = Path(...), - rfp_id: str = Path(...), - ): - try: - async with self.letter_service() as service: - data = letter.model_dump(exclude_unset=True, mode="json") - data["proposal_id"] = proposal_id - result = await service.create_letter(letter=data) - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error creating letter") - - async def update_letter(self, letter: UpdateLetterRequest, id: str = Path(...)): - try: - async with self.letter_service() as service: - result = await service.update_letter( - id=id, letter=letter.model_dump(exclude_unset=True, mode="json") - ) - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error updating letter") - - async def update_letters_by_proposal_and_id( - self, - letter: UpdateLetterRequestByProposalAndId, - proposal_id: str = Path(...), - id: str = Path(...), - rfp_id: str = Path(...), - ): - try: - async with self.letter_service() as service: - data = letter.model_dump(exclude_unset=True, mode="json") - data["proposal_id"] = proposal_id - result = await service.update_letter(id=id, letter=data) - return Response(status="success", data=[Letter(**r) for r in result]) - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error updating letter") - - async def delete_letter(self, id: str = Path(...)): - try: - async with self.letter_service() as service: - result = await service.delete_letter(id=id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error deleting letter") - - async def delete_letters_by_proposal_id( - self, proposal_id: str = Path(...), rfp_id: str = Path(...) - ): - try: - async with self.letter_service() as service: - result = await service.delete_letter(proposal_id=proposal_id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error deleting letter") - - async def delete_letters_by_proposal_and_id( - self, proposal_id: str = Path(...), id: str = Path(...), rfp_id: str = Path(...) - ): - try: - async with self.letter_service() as service: - result = await service.delete_letter(proposal_id=proposal_id, id=id) - return DeleteResponse(status="success") - except Exception as e: - logger.error(e) - raise HTTPException(status_code=500, detail="Error deleting letter") diff --git a/src/controllers/_proposal_ai_analysis_controller.py b/src/controllers/_proposal_ai_analysis_controller.py deleted file mode 100644 index b1679b59b73f396f45b05b05d93991baa6bbd707..0000000000000000000000000000000000000000 --- a/src/controllers/_proposal_ai_analysis_controller.py +++ /dev/null @@ -1,1783 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path -from pydantic import BaseModel -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger - -from src.services import ProposalAIAnalysisService - -from src.models import QueryType, CreateOrUpdateType, Category - - -class ProposalAIAnalysis(BaseModel): - id: UUID - proposal_id: UUID - ai_score: float - created_at: datetime - updated_at: datetime - - -class ProposalStrength(BaseModel): - id: UUID - proposal_id: UUID - strength_point: str - category: Category - created_at: datetime - updated_at: datetime - - -class ProposalWeakness(BaseModel): - id: UUID - proposal_id: UUID - weakness_point: str - category: Category - created_at: datetime - updated_at: datetime - - -class ProposalRejectionReason(BaseModel): - id: UUID - proposal_id: UUID - reason: str - category: Category - created_at: datetime - updated_at: datetime - - -class ProposalImprovement(BaseModel): - id: UUID - proposal_id: UUID - improvement_point: str - category: Category - created_at: datetime - updated_at: datetime - - -class ProposalAllStrengthsResponse(BaseModel): - status: str - data: Optional[List[str]] - - -class ProposalAllWeaknessesResponse(BaseModel): - status: str - data: Optional[List[str]] - - -class ProposalAllRejectionReasonsResponse(BaseModel): - status: str - data: Optional[List[str]] - - -class ProposalAllImprovementsResponse(BaseModel): - status: str - data: Optional[List[str]] - - -class ProposalAIAnalysisResponse(BaseModel): - status: str - data: Optional[List[ProposalAIAnalysis]] - - -class ProposalStrengthResponse(BaseModel): - status: str - data: Optional[List[ProposalStrength]] - - -class ProposalWeaknessResponse(BaseModel): - status: str - data: Optional[List[ProposalWeakness]] - - -class ProposalRejectionReasonResponse(BaseModel): - status: str - data: Optional[List[ProposalRejectionReason]] - - -class ProposalImprovementResponse(BaseModel): - status: str - data: Optional[List[ProposalImprovement]] - -class Analysis(BaseModel): - strengths: Optional[List[str]] = None - weaknesses: Optional[List[str]] = None - rejection_reasons: Optional[List[str]] = None - improvements: Optional[List[str]] = None - -class ProposalCompleteAnalysis(BaseModel): - ai_score: float - technical: Optional[Analysis] = None - management: Optional[Analysis] = None - past_performance: Optional[Analysis] = None - price: Optional[Analysis] = None - - -class ProposalCompleteAnalysisResponse(BaseModel): - status: str - data: Optional[List[ProposalCompleteAnalysis]] - - -class DeleteResponse(BaseModel): - status: str - - -class ProposalStrengthRequest(BaseModel): - proposal_id: UUID - strength_point: str - category: Category - - -class UpdateProposalStrengthRequest(BaseModel): - proposal_id: Optional[UUID] = None - strength_point: Optional[str] = None - category: Optional[Category] = None - - -class ProposalWeaknessRequest(BaseModel): - proposal_id: UUID - weakness_point: str - category: Category - - -class UpdateProposalWeaknessRequest(BaseModel): - proposal_id: Optional[UUID] = None - weakness_point: Optional[str] = None - category: Optional[Category] = None - - -class ProposalRejectionReasonRequest(BaseModel): - proposal_id: UUID - reason: str - category: Category - - -class UpdateProposalRejectionReasonRequest(BaseModel): - proposal_id: Optional[UUID] = None - reason: Optional[str] = None - category: Optional[Category] = None - - -class ProposalImprovementRequest(BaseModel): - proposal_id: UUID - improvement_point: str - category: Category - - -class UpdateProposalImprovementRequest(BaseModel): - proposal_id: Optional[UUID] = None - improvement_point: Optional[str] = None - category: Optional[Category] = None - - -class CreateAiAnalysisRequest(BaseModel): - proposal_id: UUID - ai_score: float - -class CreateAIAnalysisRequestByProposalId(BaseModel): - ai_score: float - -class UpdateAiAnalysisRequest(BaseModel): - proposal_id: Optional[UUID] = None - ai_score: Optional[float] = None - -class UpdateAIAnalysisRequestByProposalId(BaseModel): - ai_score: Optional[float] = None - -class CreateAllProposalStrengthsRequest(BaseModel): - strength_point: List[str] - category: Category - -class UpdateAllProposalStrengthsRequest(BaseModel): - strength_point: Optional[List[str]] = None - -class CreateAllProposalWeaknessRequest(BaseModel): - weakness_point: List[str] - category: Category - - -class CreateAllProposalRejectionReasonsRequest(BaseModel): - reason: List[str] - category: Category - - -class CreateAllProposalImprovementsRequest(BaseModel): - improvement_point: List[str] - category: Category - - -class CreateAllProposalAnalysisRequest(BaseModel): - ai_score: float - technical: Optional[Analysis] = None - management: Optional[Analysis] = None - past_performance: Optional[Analysis] = None - price: Optional[Analysis] = None - - -class UpdateAllProposalAnalysisRequest(BaseModel): - ai_score: Optional[float] = None - technical: Optional[Analysis] = None - management: Optional[Analysis] = None - past_performance: Optional[Analysis] = None - price: Optional[Analysis] = None - - -class ProposalAIController: - def __init__(self): - self.__service = ProposalAIAnalysisService - self.router = APIRouter() - - # self.router.add_api_route( - # "/proposal-strengths", - # self.get_proposal_strengths, - # methods=["GET"], - # tags=["Proposal Strengths"], - # response_model=ProposalStrengthResponse, - # ) - # self.router.add_api_route( - # "/proposal-strengths/{id}", - # self.get_proposal_strengths_by_id, - # methods=["GET"], - # tags=["Proposal Strengths by ID"], - # response_model=ProposalStrengthResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/strengths", - self.get_proposal_strengths_by_proposal_id, - methods=["GET"], - tags=["Proposal Strengths by Proposal ID"], - response_model=ProposalAllStrengthsResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{category}", - self.get_proposal_strengths_by_proposal_and_category, - methods=["GET"], - tags=["Proposal Strengths by Proposal and Category"], - response_model=ProposalAllStrengthsResponse, - ) - # self.router.add_api_route( - # "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{id}", - # self.get_proposal_strengths_by_proposal_and_id, - # methods=["GET"], - # tags=["Proposal Strengths by Proposal and ID"], - # response_model=ProposalStrengthResponse, - # ) - # self.router.add_api_route( - # "/proposal-strengths", - # self.create_proposal_strengths, - # methods=["POST"], - # tags=["Proposal Strengths"], - # response_model=ProposalStrengthResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/strengths", - self.create_all_proposal_strengths, - methods=["POST"], - tags=["Proposal Strengths by Proposal ID"], - response_model=ProposalAllStrengthsResponse, - ) - # self.router.add_api_route( - # "/proposal-strengths/{id}", - # self.update_proposal_strengths, - # methods=["PUT"], - # tags=["Proposal Strengths by ID"], - # response_model=ProposalStrengthResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{category}", - self.update_all_proposal_strengths, - methods=["PUT"], - tags=["Proposal Strengths by Proposal and Category"], - response_model=ProposalAllStrengthsResponse, - ) - # self.router.add_api_route( - # "/proposal-strengths/{id}", - # self.delete_proposal_strength, - # methods=["DELETE"], - # tags=["Proposal Strengths by ID"], - # response_model=DeleteResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/strengths", - self.delete_all_proposal_strengths, - methods=["DELETE"], - tags=["Proposal Strengths by Proposal ID"], - response_model=DeleteResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{category}", - self.delete_proposal_strengths_by_proposal_and_category, - methods=["DELETE"], - tags=["Proposal Strengths by Proposal and Category"], - response_model=DeleteResponse, - ) - # self.router.add_api_route( - # "rfps/{rfp_id}/proposals/{proposal_id}/strengths/{id}", - # self.delete_proposal_strengths_by_proposal_and_id, - # methods=["DELETE"], - # tags=["Proposal Strengths by Proposal and ID"], - # response_model=DeleteResponse, - # ) - - # self.router.add_api_route( - # "/proposal-weakness", - # self.get_proposal_weakness, - # methods=["GET"], - # tags=["Proposal Weakness"], - # response_model=ProposalWeaknessResponse, - # ) - # self.router.add_api_route( - # "/proposal-weakness/{id}", - # self.get_proposal_weakness_by_id, - # methods=["GET"], - # tags=["Proposal Weakness by ID"], - # response_model=ProposalWeaknessResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/weakness", - self.get_proposal_weakness_by_proposal_id, - methods=["GET"], - tags=["Proposal Weakness by Proposal ID"], - response_model=ProposalAllWeaknessesResponse, - ) - # self.router.add_api_route( - # "/rfps/{rfp_id}/proposals/{proposal_id}/weakness/{id}", - # self.get_proposal_weakness_by_proposal_and_id, - # methods=["GET"], - # tags=["Proposal Weakness by Proposal and ID"], - # response_model=ProposalWeaknessResponse, - # ) - # self.router.add_api_route( - # "/proposal-weakness", - # self.create_proposal_weakness, - # methods=["POST"], - # tags=["Proposal Weakness"], - # response_model=ProposalWeaknessResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/weakness", - self.create_all_proposal_weakness, - methods=["POST"], - tags=["Proposal Weakness by Proposal ID"], - response_model=ProposalAllWeaknessesResponse, - ) - # self.router.add_api_route( - # "/proposal-weakness/{id}", - # self.update_proposal_weakness, - # methods=["PUT"], - # tags=["Proposal Weakness by ID"], - # response_model=ProposalWeaknessResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/weakness", - self.update_all_proposal_weakness, - methods=["PUT"], - tags=["Proposal Weakness by Proposal ID"], - response_model=ProposalAllWeaknessesResponse, - ) - # self.router.add_api_route( - # "/proposal-weakness/{id}", - # self.delete_proposal_weakness, - # methods=["DELETE"], - # tags=["Proposal Weakness by ID"], - # response_model=DeleteResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/weakness", - self.delete_all_proposal_weakness, - methods=["DELETE"], - tags=["Proposal Weakness by Proposal ID"], - response_model=DeleteResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/weakness/{category}", - self.delete_proposal_weakness_by_proposal_and_category, - methods=["DELETE"], - tags=["Proposal Weakness by Proposal and Category"], - response_model=DeleteResponse, - ) - # self.router.add_api_route( - # "/rfps/{rfp_id}/proposals/{proposal_id}/weakness/{id}", - # self.delete_proposal_weakness_by_proposal_and_id, - # methods=["DELETE"], - # tags=["Proposal Weakness by Proposal and ID"], - # response_model=DeleteResponse, - # ) - - # self.router.add_api_route( - # "/proposal-rejections", - # self.get_proposal_rejections, - # methods=["GET"], - # tags=["Proposal Rejections"], - # response_model=ProposalRejectionReasonResponse, - # ) - # self.router.add_api_route( - # "/proposal-rejections/{id}", - # self.get_proposal_rejections_by_id, - # methods=["GET"], - # tags=["Proposal Rejections by ID"], - # response_model=ProposalRejectionReasonResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/rejections", - self.get_proposal_rejections_by_proposal_id, - methods=["GET"], - tags=["Proposal Rejections by Proposal ID"], - response_model=ProposalAllRejectionReasonsResponse, - ) - # self.router.add_api_route( - # "/rfps/{rfp_id}/proposals/{proposal_id}/rejections/{id}", - # self.get_proposal_rejections_by_proposal_and_id, - # methods=["GET"], - # tags=["Proposal Rejections by Proposal and ID"], - # response_model=ProposalRejectionReasonResponse, - # ) - # self.router.add_api_route( - # "/proposal-rejections", - # self.create_proposal_rejections, - # methods=["POST"], - # tags=["Proposal Rejections"], - # response_model=ProposalRejectionReasonResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/rejections", - self.create_all_proposal_rejections, - methods=["POST"], - tags=["Proposal Rejections by Proposal ID"], - response_model=ProposalAllRejectionReasonsResponse, - ) - # self.router.add_api_route( - # "/proposal-rejections/{id}", - # self.update_proposal_rejections, - # methods=["PUT"], - # tags=["Proposal Rejections by ID"], - # response_model=ProposalRejectionReasonResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/rejections", - self.update_all_proposal_rejections, - methods=["PUT"], - tags=["Proposal Rejections by Proposal ID"], - response_model=ProposalAllRejectionReasonsResponse, - ) - # self.router.add_api_route( - # "/proposal-rejections/{id}", - # self.delete_proposal_rejections, - # methods=["DELETE"], - # tags=["Proposal Rejections by ID"], - # response_model=DeleteResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/rejections", - self.delete_all_proposal_rejections, - methods=["DELETE"], - tags=["Proposal Rejections by Proposal ID"], - response_model=DeleteResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/rejections/{category}", - self.delete_proposal_rejections_by_proposal_and_category, - methods=["DELETE"], - tags=["Proposal Rejections by Proposal and Category"], - response_model=DeleteResponse, - ) - # self.router.add_api_route( - # "/rfps/{rfp_id}/proposals/{proposal_id}/proposal-rejections/{id}", - # self.delete_proposal_rejections_by_proposal_and_id, - # methods=["DELETE"], - # tags=["Proposal Rejections by Proposal and ID"], - # response_model=DeleteResponse, - # ) - - # self.router.add_api_route( - # "/proposal-improvements", - # self.get_proposal_improvements, - # methods=["GET"], - # tags=["Proposal Improvements"], - # response_model=ProposalImprovementResponse, - # ) - # self.router.add_api_route( - # "/proposal-improvements/{id}", - # self.get_proposal_improvements_by_id, - # methods=["GET"], - # tags=["Proposal Improvements by ID"], - # response_model=ProposalImprovementResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/improvements", - self.get_proposal_improvements_by_proposal_id, - methods=["GET"], - tags=["Proposal Improvements by Proposal ID"], - response_model=ProposalAllImprovementsResponse, - ) - # self.router.add_api_route( - # "/proposals/{proposal_id}/proposal-improvements/{id}", - # self.get_proposal_improvements_by_proposal_and_id, - # methods=["GET"], - # tags=["Proposal Improvements by Proposal and ID"], - # response_model=ProposalImprovementResponse, - # ) - # self.router.add_api_route( - # "/proposal-improvements", - # self.create_proposal_improvements, - # methods=["POST"], - # tags=["Proposal Improvements"], - # response_model=ProposalImprovementResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/improvements", - self.create_all_proposal_improvements, - methods=["POST"], - tags=["Proposal Improvements by Proposal ID"], - response_model=ProposalAllImprovementsResponse, - ) - # self.router.add_api_route( - # "/proposal-improvements/{id}", - # self.update_proposal_improvements, - # methods=["PUT"], - # tags=["Proposal Improvements by ID"], - # response_model=ProposalImprovementResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/improvements", - self.update_all_proposal_improvements, - methods=["PUT"], - tags=["Proposal Improvements by Proposal ID"], - response_model=ProposalAllImprovementsResponse, - ) - # self.router.add_api_route( - # "/proposal-improvements/{id}", - # self.delete_proposal_improvements, - # methods=["DELETE"], - # tags=["Proposal Improvements by ID"], - # response_model=DeleteResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/improvements", - self.delete_all_proposal_improvements, - methods=["DELETE"], - tags=["Proposal Improvements by Proposal ID"], - response_model=DeleteResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/improvements/{category}", - self.delete_proposal_improvements_by_proposal_and_category, - methods=["DELETE"], - tags=["Proposal Improvements by Proposal and Category"], - response_model=DeleteResponse, - ) - # self.router.add_api_route( - # "/proposals/{proposal_id}/proposal-improvements/{id}", - # self.delete_proposal_improvements_by_proposal_and_id, - # methods=["DELETE"], - # tags=["Proposal Improvements by Proposal and ID"], - # response_model=DeleteResponse, - # ) - - # self.router.add_api_route( - # "/proposal-ai-score", - # self.get_proposal_ai_score, - # methods=["GET"], - # tags=["Proposal AI Score"], - # response_model=ProposalAIAnalysisResponse, - # ) - # self.router.add_api_route( - # "/proposal-ai-score/{id}", - # self.get_proposal_ai_score_by_id, - # methods=["GET"], - # tags=["Proposal AI Score by ID"], - # response_model=ProposalAIAnalysisResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score", - self.get_proposal_ai_score_by_proposal_id, - methods=["GET"], - tags=["Proposal AI Score by Proposal ID"], - response_model=ProposalAIAnalysisResponse, - ) - # self.router.add_api_route( - # "/proposals/{proposal_id}/proposal-ai-score/{id}", - # self.get_proposal_ai_score_by_proposal_and_id, - # methods=["GET"], - # tags=["Proposal AI Score by Proposal and ID"], - # response_model=ProposalAIAnalysisResponse, - # ) - # self.router.add_api_route( - # "/proposal-ai-score", - # self.create_proposal_ai_score, - # methods=["POST"], - # tags=["Proposal AI Score"], - # response_model=ProposalAIAnalysisResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score", - self.create_proposal_ai_score_by_proposal_id, - methods=["POST"], - tags=["Proposal AI Score by Proposal ID"], - response_model=ProposalAIAnalysisResponse, - ) - # self.router.add_api_route( - # "/proposal-ai-score/{id}", - # self.update_proposal_ai_score, - # methods=["PUT"], - # tags=["Proposal AI Score by ID"], - # response_model=ProposalAIAnalysisResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score", - self.update_proposal_ai_score_by_proposal_id, - methods=["PUT"], - tags=["Proposal AI Score by Proposal ID"], - response_model=ProposalAIAnalysisResponse, - ) - # self.router.add_api_route( - # "/proposal-ai-score/{id}", - # self.delete_proposal_ai_score, - # methods=["DELETE"], - # tags=["Proposal AI Score by ID"], - # response_model=DeleteResponse, - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score", - self.delete_proposal_ai_score_by_proposal_id, - methods=["DELETE"], - tags=["Proposal AI Score by Proposal ID"], - response_model=DeleteResponse, - ) - # self.router.add_api_route( - # "/proposals/{proposal_id}/proposal-ai-score/{id}", - # self.delete_proposal_ai_score_by_proposal_and_id, - # methods=["DELETE"], - # tags=["Proposal AI Score by Proposal and ID"], - # response_model=DeleteResponse, - # ) - - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/analysis", - self.get_all_proposal_analysis, - methods=["GET"], - tags=["Proposal Complete Analysis"], - response_model=ProposalCompleteAnalysisResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/analysis", - self.create_proposal_analysis, - methods=["POST"], - tags=["Proposal Complete Analysis"], - response_model=ProposalCompleteAnalysisResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/analysis", - self.update_proposal_analysis, - methods=["PUT"], - tags=["Proposal Complete Analysis"], - response_model=ProposalCompleteAnalysisResponse, - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/analysis", - self.delete_proposal_analysis, - methods=["DELETE"], - tags=["Proposal Complete Analysis"], - response_model=DeleteResponse, - ) - - async def get_proposal_strengths( - self - ): - async with self.__service() as service: - try: - strengths = await service.get_all_data( - query_type=QueryType.STRENGTHS - ) - return ProposalStrengthResponse( - status="success", data=[ProposalStrength(**s) for s in strengths] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve strengths." - ) - - async def get_proposal_strengths_by_id(self, id: UUID = Path(...)): - async with self.__service() as service: - try: - strength = await service.get_all_data( - query_type=QueryType.STRENGTHS, id=id - ) - return ProposalStrengthResponse( - status="success", data=[ProposalStrength(**s) for s in strength] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve strength." - ) - - async def get_proposal_strengths_by_proposal_id(self, rfp_id: UUID = Path(...), proposal_id: UUID = Path(...)): - async with self.__service() as service: - try: - strength = await service.get_all_data( - query_type=QueryType.STRENGTHS, proposal_id=proposal_id - ) - return ProposalAllStrengthsResponse( - status="success", data=[s["strength_point"] for s in strength] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve strength." - ) - - async def get_proposal_strengths_by_proposal_and_category(self, rfp_id: UUID = Path(...), proposal_id: UUID = Path(...), category: Category = Path(...)): - async with self.__service() as service: - try: - strength = await service.get_all_data( - query_type=QueryType.STRENGTHS, proposal_id=proposal_id, category=category - ) - return ProposalAllStrengthsResponse( - status="success", data=[s["strength_point"] for s in strength] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve strength." - ) - - async def get_proposal_strengths_by_proposal_and_id(self, rfp_id: UUID = Path(...), proposal_id: UUID = Path(...), id: UUID = Path(...)): - async with self.__service() as service: - try: - strength = await service.get_all_data( - query_type=QueryType.STRENGTHS, proposal_id=proposal_id, id=id - ) - return ProposalStrengthResponse( - status="success", data=[ProposalStrength(**s) for s in strength] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve strength." - ) - - async def create_proposal_strengths(self, strength: ProposalStrengthRequest): - async with self.__service() as service: - try: - created = await service.create_data( - query_type=QueryType.STRENGTHS, - create_or_update_type=CreateOrUpdateType.ID, - data=strength.model_dump(mode="json"), - ) - return ProposalStrengthResponse( - status="success", data=[ProposalStrength(**s) for s in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create strength." - ) - - async def create_all_proposal_strengths( - self, strengths: CreateAllProposalStrengthsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - strengths_request = strengths.model_dump(mode="json", exclude_unset=True) - strengths_request["proposal_id"] = proposal_id - created = await service.create_data( - query_type=QueryType.STRENGTHS, - create_or_update_type=CreateOrUpdateType.ALL, - data=strengths_request, - ) - return ProposalAllStrengthsResponse( - status="success", data=[s["strength_point"] for s in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create strengths." - ) - - async def update_proposal_strengths(self, strength: UpdateProposalStrengthRequest, id: UUID = Path(...)): - async with self.__service() as service: - try: - strength_request = strength.model_dump(mode="json", exclude_unset=True) - strength_request["id"] = id - updated = await service.update_data( - query_type=QueryType.STRENGTHS, - create_or_update_type=CreateOrUpdateType.ID, - data=strength_request, - ) - return ProposalStrengthResponse( - status="success", data=[ProposalStrength(**s) for s in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update strength." - ) - - async def update_all_proposal_strengths( - self, strengths: UpdateAllProposalStrengthsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Path(...) - ): - async with self.__service() as service: - try: - strengths_request = strengths.model_dump(mode="json", exclude_unset=True) - strengths_request["proposal_id"] = proposal_id - strengths_request["category"] = category - updated = await service.update_data( - query_type=QueryType.STRENGTHS, - create_or_update_type=CreateOrUpdateType.ALL, - data=strengths_request, - ) - return ProposalAllStrengthsResponse( - status="success", data=[s["strength_point"] for s in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update strengths." - ) - - async def delete_proposal_strength( - self, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.STRENGTHS, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete strength." - ) - - async def delete_all_proposal_strengths(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.STRENGTHS, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete strengths." - ) - - async def delete_proposal_strengths_by_proposal_and_category(self, proposal_id: UUID = Path(...), category: Category = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.STRENGTHS, proposal_id=proposal_id, category=category - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete strength." - ) - - async def delete_proposal_strengths_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.STRENGTHS, proposal_id=proposal_id, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete strength." - ) - - async def get_proposal_weakness( - self - ): - async with self.__service() as service: - try: - weaknesses = await service.get_all_data( - query_type=QueryType.WEAKNESSES - ) - return ProposalWeaknessResponse( - status="success", data=[ProposalWeakness(**w) for w in weaknesses] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve weaknesses." - ) - - async def get_proposal_weakness_by_id(self, id: UUID = Path(...)): - async with self.__service() as service: - try: - weakness = await service.get_all_data( - query_type=QueryType.WEAKNESSES, id=id - ) - return ProposalWeaknessResponse( - status="success", data=[ProposalWeakness(**w) for w in weakness] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve weakness." - ) - - async def get_proposal_weakness_by_proposal_id(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Query(None)): - async with self.__service() as service: - try: - weakness = await service.get_all_data( - query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, category=category - ) - return ProposalAllWeaknessesResponse( - status="success", data=[w["weakness_point"] for w in weakness] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve weakness." - ) - - async def get_proposal_weakness_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - weakness = await service.get_all_data( - query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, id=id - ) - return ProposalWeaknessResponse( - status="success", data=[ProposalWeakness(**w) for w in weakness] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve weakness." - ) - - async def create_proposal_weakness(self, weakness: ProposalWeaknessRequest): - async with self.__service() as service: - try: - created = await service.create_data( - query_type=QueryType.WEAKNESSES, - create_or_update_type=CreateOrUpdateType.ID, - data=weakness.model_dump(mode="json"), - ) - return ProposalWeaknessResponse( - status="success", data=[ProposalWeakness(**w) for w in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create weakness." - ) - - async def create_all_proposal_weakness( - self, weaknesses: CreateAllProposalWeaknessRequest, proposal_id: UUID - ): - async with self.__service() as service: - try: - weakness_request = weaknesses.model_dump(mode="json", exclude_unset=True) - weakness_request["proposal_id"] = proposal_id - created = await service.create_data( - query_type=QueryType.WEAKNESSES, - create_or_update_type=CreateOrUpdateType.ALL, - data=weakness_request, - ) - return ProposalAllWeaknessesResponse( - status="success", data=[w["weakness_point"] for w in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create weaknesses." - ) - - async def update_proposal_weakness(self, weakness: UpdateProposalWeaknessRequest, id: UUID = Path(...)): - async with self.__service() as service: - try: - weakness_request = weakness.model_dump(mode="json", exclude_unset=True) - weakness_request["id"] = id - updated = await service.update_data( - query_type=QueryType.WEAKNESSES, - create_or_update_type=CreateOrUpdateType.ID, - data=weakness_request, - ) - return ProposalWeaknessResponse( - status="success", data=[ProposalWeakness(**w) for w in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update weakness." - ) - - async def update_all_proposal_weakness( - self, weaknesses: CreateAllProposalWeaknessRequest, proposal_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - weakness_request = weaknesses.model_dump(mode="json", exclude_unset=True) - weakness_request["proposal_id"] = proposal_id - updated = await service.update_data( - query_type=QueryType.WEAKNESSES, - create_or_update_type=CreateOrUpdateType.ALL, - data=weakness_request, - ) - return ProposalAllWeaknessesResponse( - status="success", data=[w["weakness_point"] for w in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update weaknesses." - ) - - async def delete_proposal_weakness( - self, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.WEAKNESSES, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete weakness." - ) - - async def delete_all_proposal_weakness( - self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.WEAKNESSES, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete weaknesses." - ) - - async def delete_proposal_weakness_by_proposal_and_category(self, proposal_id: UUID = Path(...), category: Category = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, category=category - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete weakness." - ) - - async def delete_proposal_weakness_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete weakness." - ) - - async def get_proposal_rejections( - self - ): - async with self.__service() as service: - try: - rejection_reasons = await service.get_all_data( - query_type=QueryType.REJECTION_REASONS - ) - return ProposalRejectionReasonResponse( - status="success", - data=[ProposalRejectionReason(**r) for r in rejection_reasons], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve rejection reasons." - ) - - async def get_proposal_rejections_by_id(self, id: UUID = Path(...)): - async with self.__service() as service: - try: - rejection_reason = await service.get_all_data( - query_type=QueryType.REJECTION_REASONS, id=id - ) - return ProposalRejectionReasonResponse( - status="success", data=[ProposalRejectionReason(**r) for r in rejection_reason] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve rejection reason." - ) - - async def get_proposal_rejections_by_proposal_id(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Query(None)): - async with self.__service() as service: - try: - rejection_reason = await service.get_all_data( - query_type=QueryType.REJECTION_REASONS, proposal_id=proposal_id, category=category - ) - return ProposalAllRejectionReasonsResponse( - status="success", data=[r["reason"] for r in rejection_reason] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve rejection reason." - ) - - async def get_proposal_rejections_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - rejection_reason = await service.get_all_data( - query_type=QueryType.REJECTION_REASONS, proposal_id=proposal_id, id=id - ) - return ProposalRejectionReasonResponse( - status="success", data=[ProposalRejectionReason(**r) for r in rejection_reason] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve rejection reason." - ) - - async def create_proposal_rejections( - self, rejection_reason: ProposalRejectionReasonRequest - ): - async with self.__service() as service: - try: - created = await service.create_data( - query_type=QueryType.REJECTION_REASONS, - create_or_update_type=CreateOrUpdateType.ID, - data=rejection_reason.model_dump(mode="json"), - ) - return ProposalRejectionReasonResponse( - status="success", - data=[ProposalRejectionReason(**r) for r in created], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create rejection reason." - ) - - async def create_all_proposal_rejections( - self, rejection_reasons: CreateAllProposalRejectionReasonsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - rejection_reasons_data = rejection_reasons.model_dump(mode="json", exclude_unset=True) - rejection_reasons_data["proposal_id"] = proposal_id - created = await service.create_data( - query_type=QueryType.REJECTION_REASONS, - create_or_update_type=CreateOrUpdateType.ALL, - data=rejection_reasons_data, - ) - return ProposalAllRejectionReasonsResponse( - status="success", - data=[r["reason"] for r in created], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create rejection reasons." - ) - - async def update_proposal_rejections( - self, rejection_reason: UpdateProposalRejectionReasonRequest, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - rejection_reason_data = rejection_reason.model_dump(mode="json", exclude_unset=True) - rejection_reason_data["id"] = id - updated = await service.update_data( - query_type=QueryType.REJECTION_REASONS, - create_or_update_type=CreateOrUpdateType.ID, - data=rejection_reason_data, - ) - return ProposalRejectionReasonResponse( - status="success", - data=[ProposalRejectionReason(**r) for r in updated], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update rejection reason." - ) - - async def update_all_proposal_rejections( - self, rejection_reasons: CreateAllProposalRejectionReasonsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - rejection_reasons_data = rejection_reasons.model_dump(mode="json", exclude_unset=True) - rejection_reasons_data["proposal_id"] = proposal_id - updated = await service.update_data( - query_type=QueryType.REJECTION_REASONS, - create_or_update_type=CreateOrUpdateType.ALL, - data=rejection_reasons_data, - ) - return ProposalAllRejectionReasonsResponse( - status="success", - data=[r["reason"] for r in updated], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update rejection reasons." - ) - - async def delete_proposal_rejections( - self, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.REJECTION_REASONS, - id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete rejection reason." - ) - - async def delete_all_proposal_rejections( - self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.REJECTION_REASONS, - proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete rejection reasons." - ) - - async def delete_proposal_rejections_by_proposal_and_category( - self, proposal_id: UUID = Path(...), category: str = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.REJECTION_REASONS, - category=category, - proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete rejection reason." - ) - - async def delete_proposal_rejections_by_proposal_and_id( - self, proposal_id: UUID = Path(...), id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.REJECTION_REASONS, - id=id, - proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete rejection reason." - ) - - async def get_proposal_improvements( - self - ): - async with self.__service() as service: - try: - improvements = await service.get_all_data( - query_type=QueryType.IMPROVEMENTS - ) - return ProposalImprovementResponse( - status="success", - data=[ProposalImprovement(**r) for r in improvements], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve improvements." - ) - - async def get_proposal_improvements_by_id( - self, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - improvements = await service.get_all_data( - query_type=QueryType.IMPROVEMENTS, - id=id - ) - return ProposalImprovementResponse( - status="success", - data=[ProposalImprovement(**r) for r in improvements], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve improvements." - ) - - async def get_proposal_improvements_by_proposal_id( - self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Query(None) - ): - async with self.__service() as service: - try: - improvements = await service.get_all_data( - query_type=QueryType.IMPROVEMENTS, - proposal_id=proposal_id, - category=category - ) - return ProposalAllImprovementsResponse( - status="success", - data=[r["improvement_point"] for r in improvements], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve improvements." - ) - - async def get_proposal_improvements_by_proposal_and_id( - self, proposal_id: UUID = Path(...), id: UUID = Path(...) - ): - async with self.__service() as service: - try: - improvements = await service.get_all_data( - query_type=QueryType.IMPROVEMENTS, - id=id, - proposal_id=proposal_id - ) - return ProposalImprovementResponse( - status="success", - data=[ProposalImprovement(**r) for r in improvements], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve improvements." - ) - - async def create_proposal_improvements( - self, improvement: ProposalImprovementRequest - ): - async with self.__service() as service: - try: - created = await service.create_data( - query_type=QueryType.IMPROVEMENTS, - create_or_update_type=CreateOrUpdateType.ID, - data=improvement.model_dump(mode="json"), - ) - return ProposalImprovementResponse( - status="success", data=[ProposalImprovement(**r) for r in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create improvement." - ) - - async def create_all_proposal_improvements( - self, improvements: CreateAllProposalImprovementsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - data = improvements.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - created = await service.create_data( - query_type=QueryType.IMPROVEMENTS, - create_or_update_type=CreateOrUpdateType.ALL, - data=data, - ) - return ProposalAllImprovementsResponse( - status="success", data=[r["improvement_point"] for r in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create improvements." - ) - - async def update_proposal_improvements( - self, improvement: UpdateProposalImprovementRequest, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - data = improvement.model_dump(mode="json", exclude_unset=True) - data["id"] = id - updated = await service.update_data( - query_type=QueryType.IMPROVEMENTS, - create_or_update_type=CreateOrUpdateType.ID, - data=data, - ) - return ProposalImprovementResponse( - status="success", data=[ProposalImprovement(**r) for r in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update improvement." - ) - - async def update_all_proposal_improvements( - self, improvements: CreateAllProposalImprovementsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - data = improvements.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - updated = await service.update_data( - query_type=QueryType.IMPROVEMENTS, - create_or_update_type=CreateOrUpdateType.ALL, - data=data, - ) - return ProposalAllImprovementsResponse( - status="success", data=[r["improvement_point"] for r in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update improvements." - ) - - async def delete_proposal_improvements( - self, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.IMPROVEMENTS, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete improvement." - ) - - async def delete_all_proposal_improvements( - self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.IMPROVEMENTS, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete improvements." - ) - - async def delete_proposal_improvements_by_proposal_and_category( - self, category: str = Path(...), proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.IMPROVEMENTS, category=category, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete improvement." - ) - - async def delete_proposal_improvements_by_proposal_and_id( - self, id: UUID = Path(...), proposal_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.IMPROVEMENTS, id=id, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete improvement." - ) - - async def get_proposal_ai_score( - self - ): - async with self.__service() as service: - try: - scores = await service.get_all_data( - query_type=QueryType.AI_SCORE - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in scores] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve AI scores." - ) - - async def get_proposal_ai_score_by_id( - self, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - scores = await service.get_all_data( - query_type=QueryType.AI_SCORE, id=id - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in scores] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve AI scores." - ) - - async def get_proposal_ai_score_by_proposal_id( - self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - scores = await service.get_all_data( - query_type=QueryType.AI_SCORE, proposal_id=proposal_id - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in scores] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve AI scores." - ) - - async def get_proposal_ai_score_by_proposal_and_id( - self, id: UUID = Path(...), proposal_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - scores = await service.get_all_data( - query_type=QueryType.AI_SCORE, id=id, proposal_id=proposal_id - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in scores] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve AI scores." - ) - - async def create_proposal_ai_score(self, score: CreateAiAnalysisRequest): - async with self.__service() as service: - try: - created = await service.create_data( - query_type=QueryType.AI_SCORE, - create_or_update_type=CreateOrUpdateType.ID, - data=score.model_dump(mode="json"), - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create AI score." - ) - - async def create_proposal_ai_score_by_proposal_id(self, score: CreateAIAnalysisRequestByProposalId, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - data = score.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - created = await service.create_data( - query_type=QueryType.AI_SCORE, - create_or_update_type=CreateOrUpdateType.ID, - data=data, - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in created] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create AI score." - ) - - async def update_proposal_ai_score(self, score: UpdateAiAnalysisRequest, id: UUID = Path(...)): - async with self.__service() as service: - try: - if id: - data = score.model_dump(mode="json", exclude_unset=True) - data["id"] = id - updated = await service.update_data( - query_type=QueryType.AI_SCORE, - create_or_update_type=CreateOrUpdateType.ID, - data=data, - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update AI score." - ) - - async def update_proposal_ai_score_by_proposal_id(self, score: UpdateAIAnalysisRequestByProposalId, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - data = score.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - updated = await service.update_data( - query_type=QueryType.AI_SCORE, - create_or_update_type=CreateOrUpdateType.ALL, - data=data, - ) - return ProposalAIAnalysisResponse( - status="success", data=[ProposalAIAnalysis(**r) for r in updated] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update AI score." - ) - - async def delete_proposal_ai_score( - self, id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.AI_SCORE, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete AI score." - ) - - async def delete_proposal_ai_score_by_proposal_id( - self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.AI_SCORE, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete AI score." - ) - - async def delete_proposal_ai_score_by_proposal_and_id( - self, id: UUID = Path(...), proposal_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.AI_SCORE, id=id, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete AI score." - ) - - async def get_all_proposal_analysis(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - analysis = await service.get_all_data( - query_type=QueryType.ALL, proposal_id=proposal_id - ) - return ProposalCompleteAnalysisResponse( - status="success", - data=[ProposalCompleteAnalysis(**analysis)], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve analysis." - ) - - async def create_proposal_analysis( - self, analysis: CreateAllProposalAnalysisRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - data = analysis.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - created = await service.create_data( - query_type=QueryType.ALL, - create_or_update_type=CreateOrUpdateType.ALL, - data=data, - ) - return ProposalCompleteAnalysisResponse( - status="success", - data=[ProposalCompleteAnalysis(**created)], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create analysis." - ) - - async def update_proposal_analysis( - self, analysis: UpdateAllProposalAnalysisRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...) - ): - async with self.__service() as service: - try: - data = analysis.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - updated = await service.update_data( - query_type=QueryType.ALL, - create_or_update_type=CreateOrUpdateType.ALL, - data=data, - ) - return ProposalCompleteAnalysisResponse( - status="success", - data=[ProposalCompleteAnalysis(**updated)], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update analysis." - ) - - async def delete_proposal_analysis(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)): - async with self.__service() as service: - try: - result = await service.delete_data( - query_type=QueryType.ALL, proposal_id=proposal_id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete analysis." - ) diff --git a/src/controllers/_proposal_controller.py b/src/controllers/_proposal_controller.py deleted file mode 100644 index b3bea8a6d70f6ea8c45e11811d712dadaab01578..0000000000000000000000000000000000000000 --- a/src/controllers/_proposal_controller.py +++ /dev/null @@ -1,338 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path -from pydantic import BaseModel, field_validator -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger -from src.services import ProposalService -from src.models import ProposalStatus, GateCriteria - - -class Proposal(BaseModel): - id: UUID - rfp_id: UUID - name: str - tep: str - gate_criteria: GateCriteria - status: ProposalStatus - ai_score: Optional[float] = None - final_score: Optional[float] = None - created_at: datetime - updated_at: datetime - - @field_validator("ai_score", "final_score") - @classmethod - def round_to_two_decimals(cls, v): - if v is not None: - return round(v, 2) - return v - -class ProposalRequest(BaseModel): - rfp_id: UUID - name: str - tep: str - gate_criteria: GateCriteria - status: ProposalStatus - ai_score: Optional[float] = None - final_score: Optional[float] = None - - -class CreateProposalRequest(BaseModel): - name: str - tep: str - gate_criteria: GateCriteria - status: ProposalStatus - ai_score: Optional[float] = None - final_score: Optional[float] = None - - -class ProposalUpdateRequest(BaseModel): - rfp_id: Optional[UUID] = None - name: Optional[str] = None - tep: Optional[str] = None - gate_criteria: Optional[GateCriteria] = None - status: Optional[ProposalStatus] = None - ai_score: Optional[float] = None - final_score: Optional[float] = None - - -class UpdateProposalRequest(BaseModel): - name: Optional[str] = None - tep: Optional[str] = None - gate_criteria: Optional[GateCriteria] = None - status: Optional[ProposalStatus] = None - ai_score: Optional[float] = None - final_score: Optional[float] = None - - -class ProposalDeleteResponse(BaseModel): - status: str - - -class ResponseProposal(BaseModel): - status: str - data: Optional[List[Proposal]] - - -class ProposalController: - def __init__(self): - self.__proposal_service = ProposalService - self.router = APIRouter() - # self.router.add_api_route( - # "/proposals", - # self.get_proposals, - # methods=["GET"], - # response_model=ResponseProposal, - # tags=["Proposals"], - # ) - # self.router.add_api_route( - # "/proposals/{proposal_id}", - # self.get_proposals_by_id, - # methods=["GET"], - # response_model=ResponseProposal, - # tags=["Proposals by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals", - self.get_proposals_by_rfp_id, - methods=["GET"], - response_model=ResponseProposal, - tags=["Proposals by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}", - self.get_proposals_by_proposal_and_rfp_id, - methods=["GET"], - response_model=ResponseProposal, - tags=["Proposals by Proposal and RFP ID"], - ) - # self.router.add_api_route( - # "/proposals", - # self.create_proposal, - # methods=["POST"], - # response_model=ResponseProposal, - # tags=["Proposals"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals", - self.create_proposal_by_rfp_id, - methods=["POST"], - response_model=ResponseProposal, - tags=["Proposals by RFP ID"], - ) - # self.router.add_api_route( - # "/proposals/{proposal_id}", - # self.update_proposal, - # methods=["PUT"], - # response_model=ResponseProposal, - # tags=["Proposals by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}", - self.update_proposal_by_proposal_and_rfp_id, - methods=["PUT"], - response_model=ResponseProposal, - tags=["Proposals by Proposal and RFP ID"], - ) - # self.router.add_api_route( - # "/proposals/{proposal_id}", - # self.delete_proposal, - # methods=["DELETE"], - # response_model=ProposalDeleteResponse, - # tags=["Proposals by ID"], - # ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals", - self.delete_proposals_by_rfp_id, - methods=["DeLETE"], - response_model=ProposalDeleteResponse, - tags=["Proposals by RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}", - self.delete_proposals_by_proposal_and_rfp_id, - methods=["DeLETE"], - response_model=ProposalDeleteResponse, - tags=["Proposals by Proposal and RFP ID"], - ) - - async def get_proposals(self): - async with self.__proposal_service() as service: - try: - proposals = await service.get_proposals() - return ResponseProposal( - status="success", data=[Proposal(**p) for p in proposals] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve proposals." - ) - - async def get_proposals_by_id( - self, - proposal_id: str = Path(...), - ): - async with self.__proposal_service() as service: - try: - proposals = await service.get_proposals(proposal_id=proposal_id) - return ResponseProposal( - status="success", data=[Proposal(**p) for p in proposals] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve proposals." - ) - - async def get_proposals_by_rfp_id( - self, - rfp_id: str = Path(...), - ): - async with self.__proposal_service() as service: - try: - proposals = await service.get_proposals(rfp_id=rfp_id) - return ResponseProposal( - status="success", data=[Proposal(**p) for p in proposals] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve proposals." - ) - - async def get_proposals_by_proposal_and_rfp_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - ): - async with self.__proposal_service() as service: - try: - proposals = await service.get_proposals( - rfp_id=rfp_id, proposal_id=proposal_id - ) - return ResponseProposal( - status="success", data=[Proposal(**p) for p in proposals] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to retrieve proposals." - ) - - async def create_proposal(self, proposal: ProposalRequest): - async with self.__proposal_service() as service: - try: - new_proposal = await service.create_proposal( - proposal.model_dump(mode="json") - ) - return ResponseProposal( - status="success", data=[Proposal(**new_proposal)] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create proposal." - ) - - async def create_proposal_by_rfp_id( - self, proposal: CreateProposalRequest, rfp_id: str = Path(...) - ): - async with self.__proposal_service() as service: - try: - data = proposal.model_dump(exclude_unset=True, mode="json") - data["rfp_id"] = rfp_id - new_proposal = await service.create_proposal(proposal=data) - return ResponseProposal( - status="success", data=[Proposal(**new_proposal)] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create proposal." - ) - - async def update_proposal( - self, proposal: ProposalUpdateRequest, proposal_id: str = Path(...) - ): - async with self.__proposal_service() as service: - try: - updated_proposal = await service.update_proposal( - proposal_id=proposal_id, - proposal=proposal.model_dump(exclude_unset=True), - ) - return ResponseProposal( - status="success", data=[Proposal(**updated_proposal)] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update proposal." - ) - - async def update_proposal_by_proposal_and_rfp_id( - self, - proposal: UpdateProposalRequest, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - ): - async with self.__proposal_service() as service: - try: - data = proposal.model_dump(exclude_unset=True, mode="json") - data["rfp_id"] = rfp_id - updated_proposal = await service.update_proposal( - proposal_id=proposal_id, - proposal=proposal.model_dump(exclude_unset=True), - ) - return ResponseProposal( - status="success", data=[Proposal(**updated_proposal)] - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update proposal." - ) - - async def delete_proposal(self, proposal_id: str = Path(...)): - async with self.__proposal_service() as service: - try: - status = await service.delete_proposal(proposal_id=proposal_id) - if not status: - raise HTTPException(status_code=404, detail="Proposal not found.") - return ProposalDeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete proposal." - ) - - async def delete_proposals_by_rfp_id(self, rfp_id: str = Path(...)): - async with self.__proposal_service() as service: - try: - status = await service.delete_proposal(rfp_id=rfp_id) - if not status: - raise HTTPException(status_code=404, detail="Proposal not found.") - return ProposalDeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete proposal." - ) - - async def delete_proposals_by_proposal_and_rfp_id( - self, rfp_id: str = Path(...), proposal_id: str = Path(...) - ): - async with self.__proposal_service() as service: - try: - status = await service.delete_proposal( - rfp_id=rfp_id, proposal_id=proposal_id - ) - if not status: - raise HTTPException(status_code=404, detail="Proposal not found.") - return ProposalDeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete proposal." - ) diff --git a/src/controllers/_proposal_detailed_analysis_controller.py b/src/controllers/_proposal_detailed_analysis_controller.py deleted file mode 100644 index 2f1e7d5ab79b2c873e1e2915a215493c87e21d66..0000000000000000000000000000000000000000 --- a/src/controllers/_proposal_detailed_analysis_controller.py +++ /dev/null @@ -1,291 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path -from pydantic import BaseModel -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger - -from src.models import OperationCriteria, ProposalDetailAnalysis - -from src.services import ProposalDetailedAnalysisService - - -class ProposalDetailedAnalysis(BaseModel): - id: UUID - proposal_id: UUID - ai_assessment_result: str - confidence: float - operation_criteria: OperationCriteria - created_at: datetime - updated_at: datetime - - -class ProposalDetailedAnalysisResponse(BaseModel): - status: str - data: Optional[List[ProposalDetailedAnalysis]] - - -class OperationCriteriaResult(BaseModel): - confidence: float - ai_assessment_result: str - - -class ProposalCompleteDetailedAnalysis(BaseModel): - mission_alignment: Optional[OperationCriteriaResult] = None - technical_feasibility: Optional[OperationCriteriaResult] = None - cost_efficiency: Optional[OperationCriteriaResult] = None - risk_assessment: Optional[OperationCriteriaResult] = None - - -class CreateProposalDetailedAnalysisRequest(BaseModel): - data: ProposalCompleteDetailedAnalysis - - -class CompleteAnalysisResponse(BaseModel): - status: str - data: Optional[ProposalCompleteDetailedAnalysis] - - -class DeleteResponse(BaseModel): - status: str - - -class CreateDetailedAnalysisRequest(BaseModel): - proposal_id: UUID - confidence: float - ai_assessment_result: str - operation_criteria: OperationCriteria - - -class UpdateDetailedAnalysisRequest(BaseModel): - proposal_id: Optional[UUID] = None - confidence: Optional[float] = None - ai_assessment_result: Optional[str] = None - operation_criteria: Optional[OperationCriteria] = None - - -class ProposalDetailedController: - def __init__(self): - self.service = ProposalDetailedAnalysisService - self.router = APIRouter() - self.router.add_api_route( - "/proposal-detailed-analysis", - self.get_proposal_analysis, - methods=["GET"], - tags=["Proposal Detailed Analysis"], - response_model=ProposalDetailedAnalysisResponse, - ) - self.router.add_api_route( - "/proposal-detailed-analysis/{id}", - self.get_proposal_analysis_by_id, - methods=["GET"], - tags=["Proposal Detailed Analysis by ID"], - response_model=ProposalDetailedAnalysisResponse, - ) - self.router.add_api_route( - "/proposals/{proposal_id}/proposal-detailed-analysis", - self.get_proposal_analysis_by_proposal_id, - methods=["GET"], - tags=["Proposal Detailed Analysis by Proposal ID"], - response_model=CompleteAnalysisResponse, - ) - self.router.add_api_route( - "/proposal-detailed-analysis", - self.create_proposal_analysis, - methods=["POST"], - tags=["Proposal Detailed Analysis"], - response_model=ProposalDetailedAnalysisResponse, - ) - self.router.add_api_route( - "/proposals/{proposal_id}/proposal-detailed-analysis", - self.create_complete_proposal_analysis, - methods=["POST"], - tags=["Proposal Detailed Analysis by Proposal ID"], - response_model=CompleteAnalysisResponse, - ) - self.router.add_api_route( - "/proposal-detailed-analysis/{id}", - self.update_proposal_analysis, - methods=["PUT"], - tags=["Proposal Detailed Analysis by ID"], - response_model=ProposalDetailedAnalysisResponse, - ) - self.router.add_api_route( - "/proposals/{proposal_id}/proposal-detailed-analysis", - self.update_complete_proposal_analysis, - methods=["PUT"], - tags=["Proposal Detailed Analysis by Proposal ID"], - response_model=CompleteAnalysisResponse, - ) - self.router.add_api_route( - "/proposal-detailed-analysis/{id}", - self.delete_proposal_analysis, - methods=["DELETE"], - tags=["Proposal Detailed Analysis by ID"], - response_model=DeleteResponse, - ) - self.router.add_api_route( - "/proposals/{proposal_id}/proposal-detailed-analysis", - self.delete_proposal_analysis_by_proposal_id, - methods=["DELETE"], - tags=["Proposal Detailed Analysis by Proposal ID"], - response_model=DeleteResponse, - ) - self.router.add_api_route( - "/proposals/{proposal_id}/proposal-detailed-analysis/{id}", - self.delete_proposal_analysis_by_proposal_and_id, - methods=["DELETE"], - tags=["Proposal Detailed Analysis by Proposal and ID"], - response_model=DeleteResponse, - ) - - async def get_proposal_analysis(self): - async with self.service() as service: - try: - result = await service.get_proposal_analysis() - if not result: - return ProposalDetailedAnalysisResponse(status="success", data=[]) - return ProposalDetailedAnalysisResponse( - status="success", - data=[ProposalDetailedAnalysis(**r) for r in result], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to get proposal analysis." - ) - - async def get_proposal_analysis_by_id(self, id: str = Path(...)): - async with self.service() as service: - try: - result = await service.get_proposal_analysis(id=id) - return ProposalDetailedAnalysisResponse( - status="success", - data=[ProposalDetailedAnalysis(**r) for r in result], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to get proposal analysis." - ) - - async def get_proposal_analysis_by_proposal_id(self, proposal_id: str = Path(...)): - async with self.service() as service: - try: - result = await service.get_proposal_analysis_by_proposal_id( - proposal_id=proposal_id - ) - return CompleteAnalysisResponse(status="success", data=result) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to get proposal analysis." - ) - - async def create_proposal_analysis(self, analysis: CreateDetailedAnalysisRequest): - async with self.service() as service: - try: - result = await service.create_proposal_analysis( - data=analysis.model_dump(mode="json", exclude_unset=True) - ) - return ProposalDetailedAnalysisResponse( - status="success", - data=[ProposalDetailedAnalysis(**r) for r in result], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create proposal analysis." - ) - - async def create_complete_proposal_analysis( - self, - analysis: CreateProposalDetailedAnalysisRequest, - proposal_id: UUID = Path(...), - ): - async with self.service() as service: - try: - data = analysis.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - result = await service.create_complete_proposal_analysis(data=data) - return CompleteAnalysisResponse(status="success", data=result) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to create proposal analysis." - ) - - async def update_proposal_analysis( - self, analysis: UpdateDetailedAnalysisRequest, id: UUID = Path(...) - ): - async with self.service() as service: - try: - data = analysis.model_dump(mode="json", exclude_unset=True) - data["id"] = id - result = await service.update_proposal_analysis(data=data) - return ProposalDetailedAnalysisResponse( - status="success", - data=[ProposalDetailedAnalysis(**r) for r in result], - ) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update proposal analysis." - ) - - async def update_complete_proposal_analysis( - self, - analysis: CreateProposalDetailedAnalysisRequest, - proposal_id: UUID = Path(...), - ): - async with self.service() as service: - try: - data = analysis.model_dump(mode="json", exclude_unset=True) - data["proposal_id"] = proposal_id - result = await service.update_complete_proposal_analysis(data=data) - return CompleteAnalysisResponse(status="success", data=result) - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to update proposal analysis." - ) - - async def delete_proposal_analysis(self, id: str = Path(...)): - async with self.service() as service: - try: - result = await service.delete_proposal_analysis(id=id) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete proposal analysis." - ) - - async def delete_proposal_analysis_by_proposal_id( - self, proposal_id: str = Path(...) - ): - async with self.service() as service: - try: - result = await service.delete_proposal_analysis(proposal_id=proposal_id) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete proposal analysis." - ) - - async def delete_proposal_analysis_by_proposal_and_id( - self, proposal_id: str = Path(...), id: str = Path(...) - ): - async with self.service() as service: - try: - result = await service.delete_proposal_analysis( - proposal_id=proposal_id, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException( - status_code=500, detail="Failed to delete proposal analysis." - ) diff --git a/src/controllers/_proposal_evaluation_controller.py b/src/controllers/_proposal_evaluation_controller.py deleted file mode 100644 index 065ff3f204222bdc199f173835dc512bca3d1bb3..0000000000000000000000000000000000000000 --- a/src/controllers/_proposal_evaluation_controller.py +++ /dev/null @@ -1,209 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Path -from pydantic import BaseModel, field_validator -from typing import List, Optional -from uuid import UUID -from datetime import datetime - -from src.config import logger -from src.models import EvaluationType -from src.services import ProposalEvaluationService - - -class Evaluation(BaseModel): - id: UUID - proposal_id: UUID - evaluation_type: EvaluationType - ai_score: Optional[float] = None - adjusted_score: Optional[float] = None - created_at: datetime - updated_at: datetime - - @field_validator("ai_score", "adjusted_score") - @classmethod - def round_to_two_decimals(cls, v): - if v is not None: - return round(v, 2) - return v - - -class Response(BaseModel): - status: str - data: Optional[List[Evaluation]] = None - - -class RequestEvaluation(BaseModel): - evaluation_type: Optional[EvaluationType] = None - ai_score: Optional[float] = None - adjusted_score: Optional[float] = None - - -class DeleteResponse(BaseModel): - status: str - - -class CreateEvaluation(BaseModel): - evaluation_type: EvaluationType - ai_score: Optional[float] = None - adjusted_score: Optional[float] = None - - -class CreateEvaluationRequest(BaseModel): - data: List[CreateEvaluation] - - -class UpdateEvaluation(BaseModel): - evaluation_type: Optional[EvaluationType] = None - ai_score: Optional[float] = None - adjusted_score: Optional[float] = None - - -class ProposalEvaluationController: - def __init__(self): - self.service = ProposalEvaluationService - self.router = APIRouter() - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations", - self.get_evaluation_by_proposal_and_rfp_id, - methods=["GET"], - response_model=Response, - tags=["Evaluations by Proposal and RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{id}", - self.get_evaluation_by_proposal_and_rfp_id_and_id, - methods=["GET"], - response_model=Response, - tags=["Evaluations by Proposal and RFP ID and ID"], - ) - - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations", - self.create_evaluation_by_proposal_and_rfp_id, - methods=["POST"], - response_model=Response, - tags=["Evaluations by Proposal and RFP ID"], - ) - - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{id}", - self.update_evaluation_by_proposal_and_rfp_id_and_id, - methods=["PUT"], - response_model=Response, - tags=["Evaluations by Proposal and RFP ID and ID"], - ) - - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations", - self.delete_evaluation_by_proposal_and_rfp_id, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Evaluations by Proposal and RFP ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{id}", - self.delete_evaluation_by_proposal_and_rfp_id_and_id, - methods=["DELETE"], - response_model=DeleteResponse, - tags=["Evaluations by Proposal and RFP ID and ID"], - ) - - async def get_evaluation_by_proposal_and_rfp_id( - self, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - evaluation_type: Optional[EvaluationType] = Query(None), - ): - try: - async with self.service() as service: - results = await service.get_evaluations( - proposal_id=proposal_id, evaluation_type=evaluation_type - ) - return Response( - status="success", - data=[Evaluation(**result) for result in results], - ) - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve proposals.") - - async def get_evaluation_by_proposal_and_rfp_id_and_id( - self, rfp_id: str = Path(...), proposal_id: str = Path(...), id: str = Path(...) - ): - try: - async with self.service() as service: - result = await service.get_evaluations(proposal_id=proposal_id, id=id) - return Response( - status="success", - data=[Evaluation(**result) for result in result], - ) - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve proposals.") - - async def create_evaluation_by_proposal_and_rfp_id( - self, - evaluation: CreateEvaluationRequest, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - ): - try: - async with self.service() as service: - result = await service.create_evaluations( - proposal_id=proposal_id, - evaluations=evaluation.model_dump(mode="json", exclude_unset=True)[ - "data" - ], - ) - return Response( - status="success", - data=[Evaluation(**result) for result in result], - ) - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve proposals.") - - async def update_evaluation_by_proposal_and_rfp_id_and_id( - self, - evaluation: UpdateEvaluation, - rfp_id: str = Path(...), - proposal_id: str = Path(...), - id: str = Path(...), - ): - try: - async with self.service() as service: - result = await service.update_evaluations( - proposal_id=proposal_id, - id=id, - evaluations=evaluation.model_dump(mode="json", exclude_unset=True), - ) - return Response( - status="success", - data=[Evaluation(**result) for result in result], - ) - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve proposals.") - - async def delete_evaluation_by_proposal_and_rfp_id( - self, rfp_id: str = Path(...), proposal_id: str = Path(...) - ): - try: - async with self.service() as service: - result = await service.delete_evaluations(proposal_id=proposal_id) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve proposals.") - - async def delete_evaluation_by_proposal_and_rfp_id_and_id( - self, rfp_id: str = Path(...), proposal_id: str = Path(...), id: str = Path(...) - ): - try: - async with self.service() as service: - result = await service.delete_evaluations( - proposal_id=proposal_id, id=id - ) - return DeleteResponse(status="success") - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve proposals.") diff --git a/src/controllers/_rfp_controller.py b/src/controllers/_rfp_controller.py deleted file mode 100644 index 7aea4d325223a16b61a2c6e8e20b1103a633ca03..0000000000000000000000000000000000000000 --- a/src/controllers/_rfp_controller.py +++ /dev/null @@ -1,170 +0,0 @@ -from fastapi import Path -from fastapi import APIRouter, HTTPException, Query -from pydantic import BaseModel, Field, field_validator -from typing import List, Optional -from uuid import UUID - -from src.config import logger -from src.services import RFPService -from datetime import datetime - -from src.models import RFPStatus - - -class RFP(BaseModel): - id: UUID - rfp_number: str - name: str - received_proposals: int - evaluated_count: int - awaiting_evaluation: int - target_award_date: Optional[datetime] = None - ko_name: Optional[str] = None - award_type: Optional[str] = None - google_drive_id: Optional[str] = None - status: RFPStatus - comparative_weights_locked: Optional[bool] = False - created_at: datetime - updated_at: datetime - - -class ResponseRFP(BaseModel): - status: str - data: Optional[List[RFP]] - - -class RFPRequest(BaseModel): - rfp_number: str - name: str - received_proposals: int - evaluated_count: int - target_award_date: Optional[datetime] = None - ko_name: Optional[str] = None - award_type: Optional[str] = None - google_drive_id: Optional[str] = None - status: RFPStatus - comparative_weights_locked: Optional[bool] = False - awaiting_evaluation: int - - -class RFPUpdateRequest(BaseModel): - rfp_number: Optional[str] = None - name: Optional[str] = None - received_proposals: Optional[int] = None - evaluated_count: Optional[int] = None - target_award_date: Optional[datetime] = None - ko_name: Optional[str] = None - award_type: Optional[str] = None - google_drive_id: Optional[str] = None - status: Optional[RFPStatus] = None - comparative_weights_locked: Optional[bool] = None - awaiting_evaluation: Optional[int] = None - - -class RFPDeleteResponse(BaseModel): - status: str - - -class RFPController: - def __init__(self): - self.__rfp_service = RFPService - self.router = APIRouter() - self.router.add_api_route( - "/rfps", - self.get_rfps, - methods=["GET"], - response_model=ResponseRFP, - tags=["RFPs"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}", - self.get_rfps_by_id, - methods=["GET"], - response_model=ResponseRFP, - tags=["RFPs by ID"], - ) - self.router.add_api_route( - "/rfps", - self.create_rfp, - methods=["POST"], - response_model=ResponseRFP, - tags=["RFPs"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}", - self.update_rfp, - methods=["PUT"], - response_model=ResponseRFP, - tags=["RFPs by ID"], - ) - self.router.add_api_route( - "/rfps/{rfp_id}", - self.delete_rfp, - methods=["DELETE"], - response_model=RFPDeleteResponse, - tags=["RFPs by ID"], - ) - - async def get_rfps(self, status: Optional[RFPStatus] = Query(None)): - async with self.__rfp_service() as service: - try: - rfps = await service.get_rfps(status=status) - return ResponseRFP(status="success", data=[RFP(**rfp) for rfp in rfps]) - except HTTPException as e: - logger.warning(e) - raise e - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve RFPs.") - - async def get_rfps_by_id(self, rfp_id: str = Path(...)): - async with self.__rfp_service() as service: - try: - rfps = await service.get_rfps(rfp_id=rfp_id) - return ResponseRFP(status="success", data=[RFP(**rfp) for rfp in rfps]) - except HTTPException as e: - logger.warning(e) - raise e - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to retrieve RFPs.") - - async def create_rfp(self, rfp: RFPRequest): - async with self.__rfp_service() as service: - try: - rfp = await service.create_rfp(rfp.model_dump(exclude_unset=True)) - return ResponseRFP(status="success", data=[RFP(**rfp)]) - except HTTPException as e: - logger.warning(e) - raise e - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to create RFP.") - - async def update_rfp(self, rfp: RFPUpdateRequest, rfp_id: str = Path(...)): - async with self.__rfp_service() as service: - try: - rfp = await service.update_rfp( - rfp_id=rfp_id, rfp=rfp.model_dump(exclude_unset=True) - ) - return ResponseRFP(status="success", data=[RFP(**rfp)]) - except HTTPException as e: - logger.warning(e) - raise e - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to update RFP.") - - async def delete_rfp(self, rfp_id: str = Path(...)): - async with self.__rfp_service() as service: - try: - status = await service.delete_rfp(rfp_id) - if not status: - raise HTTPException(status_code=404, detail="RFP not found.") - return RFPDeleteResponse(status="success") - except HTTPException as e: - logger.warning(e) - raise e - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Failed to delete RFP.") diff --git a/src/models/__init__.py b/src/models/__init__.py deleted file mode 100644 index c84a8793e94bffbad37bd5fb72f392052c30858d..0000000000000000000000000000000000000000 --- a/src/models/__init__.py +++ /dev/null @@ -1,50 +0,0 @@ -from ._base import Base -from ._proposal import Proposal, ProposalStatus, GateCriteria -from ._rfp import RFP, RFPStatus -from ._proposal_ai_analysis import ( - ProposalAIAnalysis, - ProposalStrength, - ProposalWeakness, - ProposalImprovement, - ProposalRejectionReason, - Category -) -from ._proposal_detailed_analysis import ProposalDetailAnalysis, OperationCriteria -from ._proposal_query_models import QueryType, CreateOrUpdateType -from ._letters import Letter, LetterType -from ._evaluation import EvaluationCriteria, EvaluationCriteriaType -from ._comparitive_weights import ComparativeWeights, Weights -from ._proposal_evaluation import Evaluations, EvaluationType -from ._analysis import Analysis, AnalysisType, AnalysisStatus - -__all__ = [ - "Base", - "Proposal", - "RFP", - "ProposalAIAnalysis", - "ProposalStrength", - "ProposalWeakness", - "ProposalImprovement", - "ProposalRejectionReason", - "ProposalDetailAnalysis", - "ProposalStatus", - "QueryType", - "CreateOrUpdateType", - "OperationCriteria", - "GateCriteria", - "Letter", - "LetterType", - "EvaluationCriteria", - "EvaluationCriteriaType", - "RFPStatus", - "Category", - "ComparativeWeights", - "Weights", - "Evaluations", - "EvaluationType", - "Analysis", - "AnalysisType", - "AnalysisStatus", -] -__version__ = "0.1.0" -__author__ = "Aryan Jain" diff --git a/src/models/_analysis.py b/src/models/_analysis.py deleted file mode 100644 index 4e0e2e50821588f45a1d6529154ab5d05ae4a152..0000000000000000000000000000000000000000 --- a/src/models/_analysis.py +++ /dev/null @@ -1,42 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - - -class AnalysisType(PyEnum): - STRENGTHS = "STRENGTHS" - WEAKNESSES = "WEAKNESSES" - DEFICIENCIES = "DEFICIENCIES" - -class AnalysisStatus(PyEnum): - ACCEPTED = "ACCEPTED" - REJECTED = "REJECTED" - PENDING = "PENDING" - -class Analysis(Base): - __tablename__ = "analysis" - id = Column(UUID(as_uuid=True), primary_key=True) - evaluation_id = Column( - UUID(as_uuid=True), - ForeignKey("evaluations.id", ondelete="CASCADE"), - nullable=False, - ) - insights = Column(String, nullable=False) - analysis_type = Column(Enum(AnalysisType), nullable=False) - status = Column(Enum(AnalysisStatus, name="analysisstatus"), nullable=False, default=AnalysisStatus.PENDING) - created_at = Column(DateTime(), nullable=False, default=func.now()) - updated_at = Column( - DateTime(), nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_base.py b/src/models/_base.py deleted file mode 100644 index 59be70308cbefd11f1c259799bffc030cac717f0..0000000000000000000000000000000000000000 --- a/src/models/_base.py +++ /dev/null @@ -1,3 +0,0 @@ -from sqlalchemy.orm import declarative_base - -Base = declarative_base() diff --git a/src/models/_comparitive_weights.py b/src/models/_comparitive_weights.py deleted file mode 100644 index e255f9d8729eefa971c370a58a97801b0734114a..0000000000000000000000000000000000000000 --- a/src/models/_comparitive_weights.py +++ /dev/null @@ -1,47 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Boolean, - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - - -class Weights(PyEnum): - NONE = "NONE" - LOW = "LOW" - MEDIUM = "MEDIUM" - HIGH = "HIGH" - - -class ComparativeWeights(Base): - __tablename__ = "comparative_weights" - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - rfp_id = Column( - UUID(as_uuid=True), - ForeignKey("rfps.id", ondelete="CASCADE"), - nullable=False, - unique=True, - ) - technical_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False) - management_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False) - past_performance_weight = Column( - Enum(Weights, name="comparativeweights"), nullable=False - ) - price_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False) - strengths_weight = Column(Float, nullable=False, default=15) - weaknesses_weight = Column(Float, nullable=False, default=5) - flag = Column(Boolean(), nullable=True, default=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_evaluation.py b/src/models/_evaluation.py deleted file mode 100644 index 29d694c71b08c902cc3b8ba91b6e4ea272611daf..0000000000000000000000000000000000000000 --- a/src/models/_evaluation.py +++ /dev/null @@ -1,34 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - - -class EvaluationCriteriaType(PyEnum): - EVALUATION = "EVALUATION" - GATE = "GATE" - - -class EvaluationCriteria(Base): - __tablename__ = "evaluation_criteria_details" - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - rfp_id = Column( - UUID(as_uuid=True), ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False - ) - evaluation_criteria = Column(String, nullable=True) - evaluation_criteria_type = Column(Enum(EvaluationCriteriaType), nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_letters.py b/src/models/_letters.py deleted file mode 100644 index b06d6c95940d8933c7dd323302b973e60e6c6910..0000000000000000000000000000000000000000 --- a/src/models/_letters.py +++ /dev/null @@ -1,36 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - - -class LetterType(PyEnum): - UO = "UO" - DEBRIEF = "DEBRIEF" - - -class Letter(Base): - __tablename__ = "letters" - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - letter = Column(String, nullable=True) - letter_type = Column(Enum(LetterType), nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_proposal.py b/src/models/_proposal.py deleted file mode 100644 index 30c5b7d7417307680e64766ee3ba0f64aa984ee8..0000000000000000000000000000000000000000 --- a/src/models/_proposal.py +++ /dev/null @@ -1,46 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - - -class ProposalStatus(PyEnum): - APPROVED = "APPROVED" - EVALUATED = "EVALUATED" - IN_REVIEW = "IN_REVIEW" - - -class GateCriteria(PyEnum): - PASS = "PASS" - FAIL = "FAIL" - IN_REVIEW = "IN_REVIEW" - - -class Proposal(Base): - __tablename__ = "proposals" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - rfp_id = Column( - UUID(as_uuid=True), ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False - ) - name = Column(String, nullable=False) - tep = Column(String, nullable=False) - gate_criteria = Column(Enum(GateCriteria), nullable=False) - status = Column(Enum(ProposalStatus), nullable=False) - ai_score = Column(Float, nullable=True) - final_score = Column(Float, nullable=True) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_proposal_ai_analysis.py b/src/models/_proposal_ai_analysis.py deleted file mode 100644 index 4864a9925cf7866c52ab6b137a7b22fffa5b4bb9..0000000000000000000000000000000000000000 --- a/src/models/_proposal_ai_analysis.py +++ /dev/null @@ -1,103 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from ._base import Base - -class Category(PyEnum): - TECHNICAL = "TECHNICAL" - MANAGEMENT = "MANAGEMENT" - PAST_PERFORMANCE = "PAST_PERFORMANCE" - PRICE = "PRICE" - -class ProposalAIAnalysis(Base): - __tablename__ = "proposal_ai_analyses" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - ai_score = Column(Float, nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) - - -class ProposalStrength(Base): - __tablename__ = "proposal_strengths" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - strength_point = Column(String, nullable=False) - category = Column(Enum(Category, name="strengthcategory"), nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) - - -class ProposalRejectionReason(Base): - __tablename__ = "proposal_rejection_reasons" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - reason = Column(String, nullable=False) - category = Column(Enum(Category, name="rejectioncategory"), nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) - - -class ProposalWeakness(Base): - __tablename__ = "proposal_weaknesses" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - weakness_point = Column(String, nullable=False) - category = Column(Enum(Category, name="weaknesscategory"), nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) - - -class ProposalImprovement(Base): - __tablename__ = "proposal_improvements" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - improvement_point = Column(String, nullable=False) - category = Column(Enum(Category, name="improvementcategory"), nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_proposal_detailed_analysis.py b/src/models/_proposal_detailed_analysis.py deleted file mode 100644 index 7ecbe8556867af1e0f9aa3bcc26af97532fc2ffc..0000000000000000000000000000000000000000 --- a/src/models/_proposal_detailed_analysis.py +++ /dev/null @@ -1,39 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - -class OperationCriteria(PyEnum): - MISSION_ALIGNMENT = "MISSION_ALIGNMENT" - TECHNICAL_FEASIBILITY = "TECHNICAL_FEASIBILITY" - COST_EFFICIENCY = "COST_EFFICIENCY" - RISK_ASSESSMENT = "RISK_ASSESSMENT" - - -class ProposalDetailAnalysis(Base): - __tablename__ = "proposal_detail_analyses" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - ai_assessment_result = Column(String, nullable=False) - confidence = Column(Float, nullable=False) - operation_criteria = Column(Enum(OperationCriteria), nullable=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_proposal_evaluation.py b/src/models/_proposal_evaluation.py deleted file mode 100644 index f305e9304039c83383137513fc25cdf445faa363..0000000000000000000000000000000000000000 --- a/src/models/_proposal_evaluation.py +++ /dev/null @@ -1,40 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - - -class EvaluationType(PyEnum): - TECHNICAL = "TECHNICAL" - MANAGEMENT = "MANAGEMENT" - PAST_PERFORMANCE = "PAST_PERFORMANCE" - PRICE = "PRICE" - - -class Evaluations(Base): - __tablename__ = "evaluations" - - id = Column(UUID(as_uuid=True), primary_key=True) - proposal_id = Column( - UUID(as_uuid=True), - ForeignKey("proposals.id", ondelete="CASCADE"), - nullable=False, - ) - evaluation_type = Column(Enum(EvaluationType), nullable=False) - ai_score = Column(Float(), nullable=True) - adjusted_score = Column(Float(), nullable=True) - created_at = Column(DateTime(), nullable=False, default=func.now()) - updated_at = Column( - DateTime(), nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/models/_proposal_query_models.py b/src/models/_proposal_query_models.py deleted file mode 100644 index 49f71b862e3d21c076342ef4fe5813e297e2d0ce..0000000000000000000000000000000000000000 --- a/src/models/_proposal_query_models.py +++ /dev/null @@ -1,13 +0,0 @@ -from enum import Enum as PyEnum - -class QueryType(PyEnum): - ALL = "ALL" - AI_SCORE = "AI_SCORE" - STRENGTHS = "STRENGTHS" - WEAKNESSES = "WEAKNESSES" - REJECTION_REASONS = "REJECTION_REASONS" - IMPROVEMENTS = "IMPROVEMENTS" - -class CreateOrUpdateType(PyEnum): - ALL = "ALL" - ID = "ID" \ No newline at end of file diff --git a/src/models/_rfp.py b/src/models/_rfp.py deleted file mode 100644 index 9933c920dc22e1a99c80aa00e0f0d9cd5cd84d54..0000000000000000000000000000000000000000 --- a/src/models/_rfp.py +++ /dev/null @@ -1,47 +0,0 @@ -from enum import Enum as PyEnum - -from sqlalchemy import ( - Boolean, - Column, - DateTime, - Enum, - Float, - ForeignKey, - Integer, - String, - func, -) -from sqlalchemy.dialects.postgresql import UUID -from pydantic import BaseModel -from ._base import Base - - -class RFPStatus(PyEnum): - ACTIVE = "ACTIVE" - IN_PROGRESS = "IN_PROGRESS" - NOT_YET_STARTED = "NOT_YET_STARTED" - CUSTOMER_HOLD = "CUSTOMER_HOLD" - UNSOLVED_ISSUES = "UNSOLVED_ISSUES" - AWARDED = "AWARDED" - PROTESTED = "PROTESTED" - - -class RFP(Base): - __tablename__ = "rfps" - - id = Column(UUID(as_uuid=True), primary_key=True, nullable=False) - rfp_number = Column(String, nullable=False) - name = Column(String, nullable=False) - received_proposals = Column(Integer, nullable=False) - evaluated_count = Column(Integer, nullable=False) - awaiting_evaluation = Column(Integer, nullable=False) - target_award_date = Column(DateTime, nullable=True) - ko_name = Column(String, nullable=True) - award_type = Column(String, nullable=True) - google_drive_id = Column(String, nullable=True) - status = Column(Enum(RFPStatus), nullable=False) - comparative_weights_locked = Column(Boolean(), nullable=True, default=False) - created_at = Column(DateTime, nullable=False, default=func.now()) - updated_at = Column( - DateTime, nullable=False, default=func.now(), onupdate=func.now() - ) diff --git a/src/repositories/__init__.py b/src/repositories/__init__.py deleted file mode 100644 index 3e016ba63674f35e125f1e4f8314e7af7d5cd7a1..0000000000000000000000000000000000000000 --- a/src/repositories/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -from ._base_repository import BaseRepository -from ._rfp_repository import RFPRepository -from ._proposal_repository import ProposalRepository -from ._proposal_detailed_analysis_repository import ProposalDetailedAnalysisRepository -from ._letter_repository import LetterRepository -from ._evaluation_repository import EvaluationRepository -from ._comparative_weight_repository import ComparativeWeightRepository -from ._proposal_evaluation_repository import ProposalEvaluationRepository -from ._analysis_repository import AnalysisRepository - -__all__ = [ - "BaseRepository", - "RFPRepository", - "ProposalRepository", - "ProposalDetailedAnalysisRepository", - "LetterRepository", - "EvaluationRepository", - "ComparativeWeightRepository", - "ProposalEvaluationRepository", - "AnalysisRepository", -] -__version__ = "0.1.0" -__author__ = "Aryan Jain" diff --git a/src/repositories/_analysis_repository.py b/src/repositories/_analysis_repository.py deleted file mode 100644 index ac7e6f97fb039fc21ab99a8b65a62fb1fbf37b92..0000000000000000000000000000000000000000 --- a/src/repositories/_analysis_repository.py +++ /dev/null @@ -1,99 +0,0 @@ -import uuid -from sqlalchemy import select -from ._base_repository import BaseRepository -from src.models import AnalysisType, Analysis -from src.utils import ScoreClient - - -class AnalysisRepository(BaseRepository): - def __init__(self): - super().__init__(Analysis) - self.score_client = ScoreClient - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_analysis( - self, - evaluation_id: str = None, - analysis_type: AnalysisType = None, - id: str = None, - ): - async with self.get_session() as session: - query = select(Analysis) - if evaluation_id is not None: - query = query.filter(Analysis.evaluation_id == evaluation_id) - if analysis_type is not None: - query = query.filter(Analysis.analysis_type == analysis_type) - if id is not None: - query = query.filter(Analysis.id == id) - result = await session.execute(query) - results = result.scalars().all() - return [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - - async def create_analysis( - self, - evaluation_id: str, - evaluation_analysis: dict, - analysis_score_type: str = None, - ): - async with self.get_session() as session: - for analysis in evaluation_analysis: - analysis["evaluation_id"] = evaluation_id - analysis["id"] = str(uuid.uuid4()) - instance = Analysis(**analysis) - session.add(instance) - await session.commit() - await session.refresh(instance) - async with self.score_client() as score_client: - await score_client.update_ai_score( - evaluation_id=evaluation_id, analysis_score_type=analysis_score_type - ) - return await self.get_analysis(evaluation_id=evaluation_id) - - async def update_analysis( - self, evaluation_id: str = None, id: str = None, proposal_analysis: dict = None - ): - async with self.get_session() as session: - if id: - query = select(Analysis).where(Analysis.id == id) - result = await session.execute(query) - instance = result.scalars().one() - for k, v in proposal_analysis.items(): - setattr(instance, k, v) - await session.commit() - await session.refresh(instance) - async with self.score_client() as score_client: - await score_client.update_ai_score(evaluation_id=evaluation_id) - return [ - { - k: v - for k, v in instance.__dict__.items() - if not k.startswith("_") - } - ] - else: - await self.delete_analysis(evaluation_id=evaluation_id) - return await self.create_analysis( - evaluation_id=evaluation_id, evaluation_analysis=proposal_analysis - ) - - async def delete_analysis(self, evaluation_id: str = None, id: str = None): - async with self.get_session() as session: - query = select(Analysis) - if evaluation_id: - query = query.filter(Analysis.evaluation_id == evaluation_id) - if id: - query = query.filter(Analysis.id == id) - result = await session.execute(query) - instances = result.scalars().all() - for instance in instances: - await session.delete(instance) - await session.commit() - return True diff --git a/src/repositories/_base_repository.py b/src/repositories/_base_repository.py deleted file mode 100644 index 7b904c57a996b52a206b029a523f24621c8d9c59..0000000000000000000000000000000000000000 --- a/src/repositories/_base_repository.py +++ /dev/null @@ -1,85 +0,0 @@ -from contextlib import asynccontextmanager -from typing import Any, Dict, Optional, Type, TypeVar - -from sqlalchemy import asc, desc -from sqlalchemy.exc import NoResultFound, SQLAlchemyError -from sqlalchemy.future import select - -from src.config import DatabaseConfig - -T = TypeVar("T") - - -class BaseRepository: - def __init__(self, model: Type[T]): - self._model = model - - @asynccontextmanager - async def get_session(self): - async with DatabaseConfig.async_session() as session: - try: - yield session - except SQLAlchemyError as e: - await session.rollback() - raise e - - async def create(self, **kwargs: Dict[str, Any]): - async with self.get_session() as session: - instance = self._model(**kwargs) - session.add(instance) - await session.commit() - await session.refresh(instance) - return instance - - async def get(self, id: Any): - async with self.get_session() as session: - result = await session.get(self._model, id) - if result is None: - raise NoResultFound(f"{self._model.__name__} not found with id {id}") - return result - - async def patch(self, id: Any, **kwargs: Dict[str, Any]): - async with self.get_session() as session: - instance = await session.get(self._model, id) - if not instance: - raise NoResultFound(f"{self._model.__name__} not found with id {id}") - for key, value in kwargs.items(): - setattr(instance, key, value) - await session.commit() - await session.refresh(instance) - return instance - - async def delete(self, id: Any): - async with self.get_session() as session: - instance = await session.get(self._model, id) - if not instance: - raise NoResultFound(f"{self._model.__name__} not found with id {id}") - await session.delete(instance) - await session.commit() - return True - - async def get_all( - self, - page: int = 1, - page_size: int = 10, - filter_by: Optional[Dict[str, Any]] = {}, - order_by: Optional[Dict[str, str]] = {}, - ): - async with self.get_session() as session: - offset = (page - 1) * page_size - query = ( - select(self._model) - .filter_by(**filter_by) - .offset(offset) - .limit(page_size) - ) - for field, direction in order_by.items(): - ordering = asc if direction == "asc" else desc - query = query.order_by(ordering(getattr(self._model, field))) - result = await session.execute(query) - return result.scalars().all() - - async def execute(self, query): - async with self.get_session() as session: - result = await session.execute(query) - return result diff --git a/src/repositories/_comparative_weight_repository.py b/src/repositories/_comparative_weight_repository.py deleted file mode 100644 index af7ff1d61d4d7457727f359abd0d9a4829f8f914..0000000000000000000000000000000000000000 --- a/src/repositories/_comparative_weight_repository.py +++ /dev/null @@ -1,76 +0,0 @@ -import uuid -from sqlalchemy import select -from ._base_repository import BaseRepository -from src.models import ComparativeWeights - - -class ComparativeWeightRepository(BaseRepository): - def __init__(self): - super().__init__(model=ComparativeWeights) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_comparative_weights(self, rfp_id: str = None, id: str = None): - async with self.get_session() as session: - query = select(ComparativeWeights) - if rfp_id: - query = query.where(ComparativeWeights.rfp_id == rfp_id) - if id: - query = query.where(ComparativeWeights.id == id) - result = await session.execute(query) - results = result.scalars().all() - return [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - - async def create_comparative_weights(self, rfp_id: str, comparative_weights: dict): - comparative_weights["rfp_id"] = rfp_id - comparative_weights["id"] = str(uuid.uuid4()) - async with self.get_session() as session: - instance = ComparativeWeights(**comparative_weights) - session.add(instance) - await session.commit() - await session.refresh(instance) - return [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - - async def update_comparative_weights( - self, id: str = None, rfp_id: str = None, comparative_weights: dict = None - ): - async with self.get_session() as session: - query = select(ComparativeWeights) - if id: - query = query.where(ComparativeWeights.id == id) - if rfp_id: - query = query.where(ComparativeWeights.rfp_id == rfp_id) - output = await session.execute(query) - instance = output.scalars().one() - if instance.flag == True: - return False - for k, v in comparative_weights.items(): - setattr(instance, k, v) - await session.commit() - await session.refresh(instance) - return [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - - async def delete_comparative_weights(self, id: str = None, rfp_id: str = None): - async with self.get_session() as session: - query = select(ComparativeWeights) - if id: - query = query.where(ComparativeWeights.id == id) - if rfp_id: - query = query.where(ComparativeWeights.rfp_id == rfp_id) - output = await session.execute(query) - instances = output.scalars().all() - for instance in instances: - await session.delete(instance) - await session.commit() - return True diff --git a/src/repositories/_evaluation_repository.py b/src/repositories/_evaluation_repository.py deleted file mode 100644 index 1362444b31193f35fad57df519a718c06c47b4f2..0000000000000000000000000000000000000000 --- a/src/repositories/_evaluation_repository.py +++ /dev/null @@ -1,99 +0,0 @@ -from ._base_repository import BaseRepository -from src.models import EvaluationCriteria, EvaluationCriteriaType - -import uuid -from sqlalchemy import select - - -class EvaluationRepository(BaseRepository): - def __init__(self): - super().__init__(EvaluationCriteria) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_criteria( - self, - id: str = None, - rfp_id: str = None, - evaluation_criteria_type: EvaluationCriteriaType = None, - ): - async with self.get_session() as session: - query = select(EvaluationCriteria) - if id: - query = query.where(EvaluationCriteria.id == id) - if rfp_id: - query = query.where(EvaluationCriteria.rfp_id == rfp_id) - if evaluation_criteria_type: - query = query.where( - EvaluationCriteria.evaluation_criteria_type - == evaluation_criteria_type - ) - output = await session.execute(query) - results = output.scalars().all() - return [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - - async def create_criteria(self, evaluation_criteria: dict): - async with self.get_session() as session: - evaluation_criteria_type = evaluation_criteria["evaluation_criteria_type"] - rfp_id = evaluation_criteria["rfp_id"] - query = select(EvaluationCriteria).where( - EvaluationCriteria.evaluation_criteria_type == evaluation_criteria_type, - EvaluationCriteria.rfp_id == rfp_id, - ) - output = await session.execute(query) - instances = output.scalars().all() - if instances: - return False - evaluation_criteria["id"] = str(uuid.uuid4()) - instance = EvaluationCriteria(**evaluation_criteria) - session.add(instance) - await session.commit() - await session.refresh(instance) - return [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - - async def update_criteria(self, id: str, evaluation_criteria: dict): - async with self.get_session() as session: - query = select(EvaluationCriteria).where(EvaluationCriteria.id == id) - output = await session.execute(query) - instance = output.scalars().one() - rfp_id = instance.rfp_id - if "evaluation_criteria_type" in evaluation_criteria: - query = select(EvaluationCriteria).where( - EvaluationCriteria.evaluation_criteria_type - == evaluation_criteria["evaluation_criteria_type"], - EvaluationCriteria.rfp_id == rfp_id, - ) - output = await session.execute(query) - instances = output.scalars().all() - if instances: - return False - for k, v in evaluation_criteria.items(): - setattr(instance, k, v) - await session.commit() - await session.refresh(instance) - return [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - - async def delete_criteria(self, id: str = None, rfp_id: str = None): - async with self.get_session() as session: - query = select(EvaluationCriteria) - if id: - query = query.where(EvaluationCriteria.id == id) - if rfp_id: - query = query.where(EvaluationCriteria.rfp_id == rfp_id) - output = await session.execute(query) - instances = output.scalars().all() - for instance in instances: - await session.delete(instance) - await session.commit() - return True diff --git a/src/repositories/_letter_repository.py b/src/repositories/_letter_repository.py deleted file mode 100644 index 22f16c9fa9a83f4da55aa270fa8e0dda79cea664..0000000000000000000000000000000000000000 --- a/src/repositories/_letter_repository.py +++ /dev/null @@ -1,93 +0,0 @@ -import uuid -from sqlalchemy import select - -from src.models import Letter, LetterType - -from ._base_repository import BaseRepository - - -class LetterRepository(BaseRepository): - def __init__(self): - super().__init__(Letter) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_letters( - self, id: str = None, proposal_id: str = None, letter_type: LetterType = None - ): - async with self.get_session() as session: - query = select(Letter) - if id: - query = query.where(Letter.id == id) - if proposal_id: - query = query.where(Letter.proposal_id == proposal_id) - if letter_type: - query = query.where(Letter.letter_type == letter_type) - output = await session.execute(query) - results = output.scalars().all() - return [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - - async def create_letter(self, letter: dict): - async with self.get_session() as session: - letter_type = letter["letter_type"] - proposal_id = letter["proposal_id"] - query = select(Letter).where( - Letter.proposal_id == proposal_id, Letter.letter_type == letter_type - ) - output = await session.execute(query) - instances = output.scalars().all() - if instances: - return False - id = str(uuid.uuid4()) - letter["id"] = id - instance = Letter(**letter) - session.add(instance) - await session.commit() - await session.refresh(instance) - return [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - - async def update_letter(self, id: str, letter: dict): - async with self.get_session() as session: - query = select(Letter).where(Letter.id == id) - output = await session.execute(query) - instance = output.scalars().one() - proposal_id = instance.proposal_id - if "letter_type" in letter: - query = select(Letter).where( - Letter.proposal_id == proposal_id, - Letter.letter_type == letter["letter_type"], - ) - output = await session.execute(query) - instances = output.scalars().all() - if instances: - return False - for key, value in letter.items(): - setattr(instance, key, value) - await session.commit() - await session.refresh(instance) - return [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - - async def delete_letter(self, id: str = None, proposal_id: str = None): - async with self.get_session() as session: - query = select(Letter) - if id: - query = query.where(Letter.id == id) - if proposal_id: - query = query.where(Letter.proposal_id == proposal_id) - output = await session.execute(query) - instances = output.scalars().all() - for instance in instances: - await session.delete(instance) - await session.commit() - return True diff --git a/src/repositories/_proposal_detailed_analysis_repository.py b/src/repositories/_proposal_detailed_analysis_repository.py deleted file mode 100644 index 791530ea1479c4a6a1bc399c865e72538591b0c5..0000000000000000000000000000000000000000 --- a/src/repositories/_proposal_detailed_analysis_repository.py +++ /dev/null @@ -1,230 +0,0 @@ -import uuid -from sqlalchemy import select -from ._base_repository import BaseRepository -from src.models import ProposalDetailAnalysis, OperationCriteria - - -class ProposalDetailedAnalysisRepository(BaseRepository): - def __init__(self): - super().__init__(model=ProposalDetailAnalysis) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_proposal_analysis(self, id: str = None): - async with self.get_session() as session: - if id: - result = await session.execute( - select(self._model).where(self._model.id == id) - ) - results = result.scalars().all() - result_dict = [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - return result_dict - else: - result = await session.execute(select(self._model)) - results = result.scalars().all() - result_dict = [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - return result_dict - - async def get_proposal_analysis_by_proposal_id(self, proposal_id: str = None): - async with self.get_session() as session: - if proposal_id: - result_o = await session.execute( - select(self._model).where(self._model.proposal_id == proposal_id) - ) - results = result_o.scalars().all() - result_dict = [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - final_result_dict = { - "mission_alignment": {"ai_assessment_result": "", "confidence": 0}, - "technical_feasibility": { - "ai_assessment_result": "", - "confidence": 0, - }, - "cost_efficiency": {"ai_assessment_result": "", "confidence": 0}, - "risk_assessment": {"ai_assessment_result": "", "confidence": 0}, - } - for result_d in result_dict: - if ( - result_d["operation_criteria"] - == OperationCriteria.MISSION_ALIGNMENT - ): - final_result_dict["mission_alignment"][ - "ai_assessment_result" - ] = result_d["ai_assessment_result"] - final_result_dict["mission_alignment"]["confidence"] = result_d[ - "confidence" - ] - elif ( - result_d["operation_criteria"] - == OperationCriteria.TECHNICAL_FEASIBILITY - ): - final_result_dict["technical_feasibility"][ - "ai_assessment_result" - ] = result_d["ai_assessment_result"] - final_result_dict["technical_feasibility"]["confidence"] = ( - result_d["confidence"] - ) - elif ( - result_d["operation_criteria"] - == OperationCriteria.COST_EFFICIENCY - ): - final_result_dict["cost_efficiency"]["ai_assessment_result"] = ( - result_d["ai_assessment_result"] - ) - final_result_dict["cost_efficiency"]["confidence"] = result_d[ - "confidence" - ] - elif ( - result_d["operation_criteria"] - == OperationCriteria.RISK_ASSESSMENT - ): - final_result_dict["risk_assessment"]["ai_assessment_result"] = ( - result_d["ai_assessment_result"] - ) - final_result_dict["risk_assessment"]["confidence"] = result_d[ - "confidence" - ] - return final_result_dict - return [] - - async def create_proposal_analysis(self, data: dict = None): - async with self.get_session() as session: - data["id"] = str(uuid.uuid4()) - instance = self._model(**data) - session.add(instance) - await session.commit() - await session.refresh(instance) - result_dict = [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - return result_dict - - async def create_complete_proposal_analysis(self, data: dict = None): - proposal_id = data["proposal_id"] - data = data["data"] - if "mission_alignment" in data: - await self.create_proposal_analysis( - data={ - "proposal_id": proposal_id, - "ai_assessment_result": data["mission_alignment"][ - "ai_assessment_result" - ], - "confidence": data["mission_alignment"]["confidence"], - "operation_criteria": OperationCriteria.MISSION_ALIGNMENT, - } - ) - if "technical_feasibility" in data: - await self.create_proposal_analysis( - data={ - "proposal_id": proposal_id, - "ai_assessment_result": data["technical_feasibility"][ - "ai_assessment_result" - ], - "confidence": data["technical_feasibility"]["confidence"], - "operation_criteria": OperationCriteria.TECHNICAL_FEASIBILITY, - } - ) - if "cost_efficiency" in data: - await self.create_proposal_analysis( - data={ - "proposal_id": proposal_id, - "ai_assessment_result": data["cost_efficiency"][ - "ai_assessment_result" - ], - "confidence": data["cost_efficiency"]["confidence"], - "operation_criteria": OperationCriteria.COST_EFFICIENCY, - } - ) - if "risk_assessment" in data: - await self.create_proposal_analysis( - data={ - "proposal_id": proposal_id, - "ai_assessment_result": data["risk_assessment"][ - "ai_assessment_result" - ], - "confidence": data["risk_assessment"]["confidence"], - "operation_criteria": OperationCriteria.RISK_ASSESSMENT, - } - ) - return await self.get_proposal_analysis_by_proposal_id(proposal_id=proposal_id) - - async def update_proposal_analysis(self, data: dict = None): - id = data["id"] - async with self.get_session() as session: - result = await session.execute( - select(self._model).where(self._model.id == id) - ) - instance = result.scalars().one() - for key, value in data.items(): - setattr(instance, key, value) - await session.commit() - await session.refresh(instance) - result_dict = [ - {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - ] - return result_dict - - async def update_complete_proposal_analysis(self, data: dict = None): - proposal_id = data["proposal_id"] - data = data["data"] - old_data = await self.get_proposal_analysis_by_proposal_id( - proposal_id=proposal_id - ) - if "mission_alignment" in data: - old_data["mission_alignment"]["ai_assessment_result"] = data[ - "mission_alignment" - ]["ai_assessment_result"] - old_data["mission_alignment"]["confidence"] = data["mission_alignment"][ - "confidence" - ] - if "technical_feasibility" in data: - old_data["technical_feasibility"]["ai_assessment_result"] = data[ - "technical_feasibility" - ]["ai_assessment_result"] - old_data["technical_feasibility"]["confidence"] = data[ - "technical_feasibility" - ]["confidence"] - if "cost_efficiency" in data: - old_data["cost_efficiency"]["ai_assessment_result"] = data[ - "cost_efficiency" - ]["ai_assessment_result"] - old_data["cost_efficiency"]["confidence"] = data["cost_efficiency"][ - "confidence" - ] - if "risk_assessment" in data: - old_data["risk_assessment"]["ai_assessment_result"] = data[ - "risk_assessment" - ]["ai_assessment_result"] - old_data["risk_assessment"]["confidence"] = data["risk_assessment"][ - "confidence" - ] - await self.delete_proposal_analysis(proposal_id=proposal_id) - old_data["data"] = old_data - old_data["proposal_id"] = proposal_id - return await self.create_complete_proposal_analysis(data=old_data) - - async def delete_proposal_analysis(self, id: str = None, proposal_id: str = None): - async with self.get_session() as session: - query = select(ProposalDetailAnalysis) - if id: - query = query.where(ProposalDetailAnalysis.id == id) - if proposal_id: - query = query.where(ProposalDetailAnalysis.proposal_id == proposal_id) - result = await session.execute(query) - instances = result.scalars().all() - for instance in instances: - await session.delete(instance) - await session.commit() - return True diff --git a/src/repositories/_proposal_evaluation_repository.py b/src/repositories/_proposal_evaluation_repository.py deleted file mode 100644 index ab99098cf5a2889da4e30c6ef62434f52844bb0d..0000000000000000000000000000000000000000 --- a/src/repositories/_proposal_evaluation_repository.py +++ /dev/null @@ -1,91 +0,0 @@ -import uuid -from sqlalchemy import select -from ._base_repository import BaseRepository -from src.models import Evaluations, EvaluationType - - -class ProposalEvaluationRepository(BaseRepository): - def __init__(self): - super().__init__(Evaluations) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_evaluations( - self, - proposal_id: str = None, - id: str = None, - evaluation_type: EvaluationType = None, - ): - async with self.get_session() as session: - query = select(Evaluations) - if proposal_id is not None: - query = query.filter(Evaluations.proposal_id == proposal_id) - if id is not None: - query = query.filter(Evaluations.id == id) - if evaluation_type is not None: - query = query.filter(Evaluations.evaluation_type == evaluation_type) - result = await session.execute(query) - results = result.scalars().all() - return [ - {k: v for k, v in result.__dict__.items() if not k.startswith("_")} - for result in results - ] - - async def craete_evaluations(self, proposal_id: str, proposal_evaluations: dict): - for evaluations in proposal_evaluations: - evaluations["proposal_id"] = proposal_id - evaluations["id"] = str(uuid.uuid4()) - async with self.get_session() as session: - query = select(Evaluations).where( - Evaluations.proposal_id == proposal_id, - Evaluations.evaluation_type == evaluations["evaluation_type"], - ) - result = await session.execute(query) - instances = result.scalars().all() - if instances: - for instance in instances: - await session.delete(instance) - await session.commit() - instance = Evaluations(**evaluations) - session.add(instance) - await session.commit() - await session.refresh(instance) - return await self.get_evaluations(proposal_id=proposal_id) - - async def update_evaluations(self, proposal_id: str, id: str, evaluations: dict): - async with self.get_session() as session: - if "evaluation_type" in evaluations: - query = select(Evaluations).where( - Evaluations.proposal_id == proposal_id, - Evaluations.evaluation_type == evaluations["evaluation_type"], - ) - result = await session.execute(query) - instance = result.scalars().all() - if instance: - return False - query = select(Evaluations).where(Evaluations.id == id) - result = await session.execute(query) - instance = result.scalars().one() - for k, v in evaluations.items(): - setattr(instance, k, v) - await session.commit() - await session.refresh(instance) - return [{k: v for k, v in instance.__dict__.items() if not k.startswith("_")}] - - async def delete_evaluations(self, proposal_id: str = None, id: str = None): - async with self.get_session() as session: - query = select(Evaluations) - if proposal_id: - query = query.where(Evaluations.proposal_id == proposal_id) - if id: - query = query.where(Evaluations.id == id) - result = await session.execute(query) - instances = result.scalars().all() - for instance in instances: - await session.delete(instance) - await session.commit() - return True diff --git a/src/repositories/_proposal_repository.py b/src/repositories/_proposal_repository.py deleted file mode 100644 index feadd1239263e5b2c8c518a2ed2f16e1481c3e53..0000000000000000000000000000000000000000 --- a/src/repositories/_proposal_repository.py +++ /dev/null @@ -1,74 +0,0 @@ -import uuid -from sqlalchemy import select -from src.models import Proposal, ProposalStatus -from ._base_repository import BaseRepository -from ._rfp_repository import RFPRepository - - -class ProposalRepository(BaseRepository): - def __init__(self): - super().__init__(model=Proposal) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_proposals( - self, proposal_id: str = None, rfp_id: str = None, status: ProposalStatus = None - ): - async with self.get_session() as session: - query = select(Proposal) - if proposal_id: - query = query.where(Proposal.id == proposal_id) - if rfp_id: - query = query.where(Proposal.rfp_id == rfp_id) - if status: - query = query.where(Proposal.status == status) - result = await session.execute(query) - final_results = result.scalars().all() - return [ - {k: v for k, v in proposal.__dict__.items() if not k.startswith("_")} - for proposal in final_results - ] - - async def create_proposal(self, proposal: dict): - proposal["id"] = str(uuid.uuid4()) - async with RFPRepository() as rfp_repository: - rfp = await rfp_repository.get_rfps(rfp_id=proposal["rfp_id"]) - if not rfp: - return None - async with self.get_session() as session: - instance = Proposal(**proposal) - session.add(instance) - await session.commit() - await session.refresh(instance) - return {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - - async def update_proposal(self, proposal_id: str, proposal: dict): - async with self.get_session() as session: - instance = await session.get(Proposal, proposal_id) - if not instance: - return None - for key, value in proposal.items(): - setattr(instance, key, value) - await session.commit() - await session.refresh(instance) - return {k: v for k, v in instance.__dict__.items() if not k.startswith("_")} - - async def delete_proposal(self, proposal_id: str = None, rfp_id: str = None): - async with self.get_session() as session: - query = select(Proposal) - if proposal_id: - query = query.where(Proposal.id == proposal_id) - if rfp_id: - query = query.where(Proposal.rfp_id == rfp_id) - result = await session.execute(query) - proposals = result.scalars().all() - if not proposals: - return False - for proposal in proposals: - await session.delete(proposal) - await session.commit() - return True diff --git a/src/repositories/_rfp_repository.py b/src/repositories/_rfp_repository.py deleted file mode 100644 index 0ec44eeb0b78450f1da4ae4d201201fb3386cdf6..0000000000000000000000000000000000000000 --- a/src/repositories/_rfp_repository.py +++ /dev/null @@ -1,67 +0,0 @@ -import uuid -from sqlalchemy import select - -from src.models import RFP, RFPStatus - -from ._base_repository import BaseRepository - - -class RFPRepository(BaseRepository): - def __init__(self): - super().__init__(model=RFP) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_rfps(self, rfp_id: str = None, status: RFPStatus = None): - async with self.get_session() as session: - query = select(RFP) - if rfp_id: - query = query.where(RFP.id == rfp_id) - if status: - query = query.where(RFP.status == status) - result = await session.execute(query) - final_results = result.scalars().all() - result_dict = [ - { - k: v - for k, v in final_result.__dict__.items() - if not k.startswith("_") - } - for final_result in final_results - ] - return result_dict - - async def create_rfp(self, rfp: dict): - rfp["id"] = str(uuid.uuid4()) - async with self.get_session() as session: - instance = RFP(**rfp) - session.add(instance) - await session.commit() - await session.refresh(instance) - rsult_dict = { - k: v for k, v in instance.__dict__.items() if not k.startswith("_") - } - return rsult_dict - - async def update_rfp(self, rfp_id: str, rfp: dict): - async with self.get_session() as session: - instance = await session.get(RFP, rfp_id) - for key, value in rfp.items(): - setattr(instance, key, value) - await session.commit() - await session.refresh(instance) - result_dict = { - k: v for k, v in instance.__dict__.items() if not k.startswith("_") - } - return result_dict - - async def delete_rfp(self, rfp_id: str): - async with self.get_session() as session: - instance = await session.get(RFP, rfp_id) - await session.delete(instance) - await session.commit() - return True diff --git a/src/services/__init__.py b/src/services/__init__.py deleted file mode 100644 index 6d687200eda8bc919ff5c0096dd0f069d70571e8..0000000000000000000000000000000000000000 --- a/src/services/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from ._rfp_service import RFPService -from ._proposal_service import ProposalService -from ._proposal_ai_analysis_service import ( - ProposalAIAnalysisService, - QueryType, - CreateOrUpdateType, -) -from ._proposal_detailed_analysis_service import ProposalDetailedAnalysisService -from ._letter_service import LetterService -from ._evaluation_service import EvaluationService -from ._comparative_weight_service import ComparativeWeightService -from ._proposal_evaluation_service import ProposalEvaluationService -from ._analysis_service import AnalysisService -from ._file_service import FileService - -__all__ = [ - "RFPService", - "ProposalService", - "ProposalAIAnalysisService", - "QueryType", - "CreateOrUpdateType", - "ProposalDetailedAnalysisService", - "LetterService", - "EvaluationService", - "ComparativeWeightService", - "ProposalEvaluationService", - "AnalysisService", -] -__version__ = "0.1.0" -__author__ = "Aryan Jain" diff --git a/src/services/_analysis_service.py b/src/services/_analysis_service.py deleted file mode 100644 index 31483fb10476f2d165357c39378b895d4c7dcf63..0000000000000000000000000000000000000000 --- a/src/services/_analysis_service.py +++ /dev/null @@ -1,49 +0,0 @@ -from src.repositories import AnalysisRepository -from src.models import AnalysisType - - -class AnalysisService: - def __init__(self): - self.analysis_repository = AnalysisRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_analysis( - self, - evaluation_id: str = None, - analysis_type: AnalysisType = None, - id: str = None, - ): - async with self.analysis_repository() as repo: - return await repo.get_analysis( - evaluation_id=evaluation_id, analysis_type=analysis_type, id=id - ) - - async def create_analysis( - self, - evaluation_id: str, - evaluation_analysis: dict, - analysis_score_type: str = None, - ): - async with self.analysis_repository() as repo: - return await repo.create_analysis( - evaluation_id=evaluation_id, - evaluation_analysis=evaluation_analysis, - analysis_score_type=analysis_score_type, - ) - - async def update_analysis( - self, evaluation_id: str = None, id: str = None, proposal_analysis: dict = None - ): - async with self.analysis_repository() as repo: - return await repo.update_analysis( - evaluation_id=evaluation_id, id=id, proposal_analysis=proposal_analysis - ) - - async def delete_analysis(self, evaluation_id: str = None, id: str = None): - async with self.analysis_repository() as repo: - return await repo.delete_analysis(evaluation_id=evaluation_id, id=id) diff --git a/src/services/_comparative_weight_service.py b/src/services/_comparative_weight_service.py deleted file mode 100644 index 9c9a810cf450af09384696750076feed3253f804..0000000000000000000000000000000000000000 --- a/src/services/_comparative_weight_service.py +++ /dev/null @@ -1,38 +0,0 @@ -from src.repositories import ComparativeWeightRepository - - -class ComparativeWeightService: - def __init__(self): - self.comparative_weight_repository = ComparativeWeightRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_comparative_weights(self, rfp_id: str = None, id: str = None): - async with self.comparative_weight_repository() as comparative_weight_repository: - return await comparative_weight_repository.get_comparative_weights( - rfp_id=rfp_id, id=id - ) - - async def create_comparative_weights(self, rfp_id: str, comparative_weights: dict): - async with self.comparative_weight_repository() as comparative_weight_repository: - return await comparative_weight_repository.create_comparative_weights( - rfp_id=rfp_id, comparative_weights=comparative_weights - ) - - async def update_comparative_weights( - self, id: str = None, rfp_id: str = None, comparative_weights: dict = None - ): - async with self.comparative_weight_repository() as comparative_weight_repository: - return await comparative_weight_repository.update_comparative_weights( - id=id, rfp_id=rfp_id, comparative_weights=comparative_weights - ) - - async def delete_comparative_weights(self, id: str = None, rfp_id: str = None): - async with self.comparative_weight_repository() as comparative_weight_repository: - return await comparative_weight_repository.delete_comparative_weights( - id=id, rfp_id=rfp_id - ) diff --git a/src/services/_evaluation_service.py b/src/services/_evaluation_service.py deleted file mode 100644 index 422334f7e35e0849aeacbd109bc25859007555d7..0000000000000000000000000000000000000000 --- a/src/services/_evaluation_service.py +++ /dev/null @@ -1,38 +0,0 @@ -from src.repositories import EvaluationRepository -from src.models import EvaluationCriteriaType - - -class EvaluationService: - def __init__(self): - self._evaluation_repository = EvaluationRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_criteria( - self, - id: str = None, - rfp_id: str = None, - evaluation_criteria_type: EvaluationCriteriaType = None, - ): - async with self._evaluation_repository() as repository: - return await repository.get_criteria( - id=id, rfp_id=rfp_id, evaluation_criteria_type=evaluation_criteria_type - ) - - async def create_criteria(self, evaluation_criteria: dict): - async with self._evaluation_repository() as repository: - return await repository.create_criteria(evaluation_criteria) - - async def update_criteria(self, id: str, evaluation_criteria: dict): - async with self._evaluation_repository() as repository: - return await repository.update_criteria( - id=id, evaluation_criteria=evaluation_criteria - ) - - async def delete_criteria(self, id: str = None, rfp_id: str = None): - async with self._evaluation_repository() as repository: - return await repository.delete_criteria(id=id, rfp_id=rfp_id) diff --git a/src/services/_file_service.py b/src/services/_file_service.py deleted file mode 100644 index 4d2da48615541a42b50fd0d959dc7e949fe1aca0..0000000000000000000000000000000000000000 --- a/src/services/_file_service.py +++ /dev/null @@ -1,35 +0,0 @@ -import io -from fastapi import File, UploadFile -from src.utils import FileClient - -class FileService: - def __init__(self): - self.file_client = FileClient - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def extract_file(self, file: UploadFile = File(...)): - file_content = await file.read() - file_bytes = io.BytesIO(file_content) - file_type = file.content_type - async with self.file_client() as client: - if file_type == "application/pdf": - return await client.extract_from_pdf(file_bytes=file_bytes) - elif file_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document": - return await client.extrcat_from_word(file_bytes=file_bytes) - elif file_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": - return await client.extract_from_excel(file_bytes=file_bytes) - elif file_type == "application/json": - return await client.extract_from_json(file_bytes=file_bytes) - elif file_type == "text/markdown": - return await client.extrcat_from_md(file_bytes=file_bytes) - elif file_type == "text/plain": - return await client.extract_from_txt(file_bytes=file_bytes) - elif file_type == "text/csv": - return await client.extract_from_csv(file_bytes=file_bytes) - else: - raise ValueError("Unsupported file type") \ No newline at end of file diff --git a/src/services/_letter_service.py b/src/services/_letter_service.py deleted file mode 100644 index d8c31cd8fb61bbb4b2418d7d2da5622ab1007631..0000000000000000000000000000000000000000 --- a/src/services/_letter_service.py +++ /dev/null @@ -1,33 +0,0 @@ -from src.repositories import LetterRepository -from src.models import Letter, LetterType - - -class LetterService: - def __init__(self): - self.letter_repository = LetterRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_letters( - self, id: str = None, proposal_id: str = None, letter_type: LetterType = None - ): - async with self.letter_repository() as repository: - return await repository.get_letters( - id=id, proposal_id=proposal_id, letter_type=letter_type - ) - - async def create_letter(self, letter: dict): - async with self.letter_repository() as repository: - return await repository.create_letter(letter) - - async def update_letter(self, id: str, letter: dict): - async with self.letter_repository() as repository: - return await repository.update_letter(id=id, letter=letter) - - async def delete_letter(self, id: str = None, proposal_id: str = None): - async with self.letter_repository() as repository: - return await repository.delete_letter(id) diff --git a/src/services/_proposal_ai_analysis_service.py b/src/services/_proposal_ai_analysis_service.py deleted file mode 100644 index 5536fd995d04c5ae8796bb847853bffa76f218be..0000000000000000000000000000000000000000 --- a/src/services/_proposal_ai_analysis_service.py +++ /dev/null @@ -1,84 +0,0 @@ -from enum import Enum as PyEnum - -from src.utils import ProposalAIAnalysisClient - -from src.models import QueryType, CreateOrUpdateType, Category - - -class ProposalAIAnalysisService: - def __init__(self): - self.proposal_ai_analysis_client = ProposalAIAnalysisClient - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_all_data( - self, - query_type: QueryType = QueryType.ALL, - id: str = None, - proposal_id: str = None, - category: Category = None, - ): - async with self.proposal_ai_analysis_client() as client: - if query_type == QueryType.ALL: - return await client.get_complete_analysis(proposal_id=proposal_id) - else: - return await client.get_all_data( - query_type=query_type, - id=id, - proposal_id=proposal_id, - category=category, - ) - - async def create_data( - self, - query_type: QueryType = QueryType.ALL, - create_or_update_type: CreateOrUpdateType = CreateOrUpdateType.ALL, - data: dict = None, - ): - async with self.proposal_ai_analysis_client() as client: - if query_type == QueryType.ALL: - return await client.create_complete_analysis(data=data) - else: - return await client.create_data( - query_type=query_type, - create_or_update_type=create_or_update_type, - data=data, - ) - - async def update_data( - self, - query_type: QueryType = QueryType.ALL, - create_or_update_type: CreateOrUpdateType = CreateOrUpdateType.ALL, - data: dict = None, - ): - async with self.proposal_ai_analysis_client() as client: - if query_type == QueryType.ALL: - return await client.update_complete_analysis(data=data) - else: - return await client.update_data( - query_type=query_type, - create_or_update_type=create_or_update_type, - data=data, - ) - - async def delete_data( - self, - query_type: QueryType = QueryType.ALL, - id: str = None, - proposal_id: str = None, - category: Category = None, - ): - async with self.proposal_ai_analysis_client() as client: - if query_type == QueryType.ALL: - return await client.delete_complete_analysis(proposal_id=proposal_id) - else: - return await client.delete_data( - query_type=query_type, - id=id, - proposal_id=proposal_id, - category=category, - ) diff --git a/src/services/_proposal_detailed_analysis_service.py b/src/services/_proposal_detailed_analysis_service.py deleted file mode 100644 index 9507c9993b56a0446f552b3942653ebf841b1737..0000000000000000000000000000000000000000 --- a/src/services/_proposal_detailed_analysis_service.py +++ /dev/null @@ -1,42 +0,0 @@ -from src.repositories import ProposalDetailedAnalysisRepository - - -class ProposalDetailedAnalysisService: - def __init__(self): - self.repository = ProposalDetailedAnalysisRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_proposal_analysis(self, id: str = None): - async with self.repository() as repository: - return await repository.get_proposal_analysis(id) - - async def get_proposal_analysis_by_proposal_id(self, proposal_id: str = None): - async with self.repository() as repository: - return await repository.get_proposal_analysis_by_proposal_id(proposal_id) - - async def create_proposal_analysis(self, data: dict = None): - async with self.repository() as repository: - return await repository.create_proposal_analysis(data) - - async def create_complete_proposal_analysis(self, data: dict = None): - async with self.repository() as repository: - return await repository.create_complete_proposal_analysis(data) - - async def update_proposal_analysis(self, data: dict = None): - async with self.repository() as repository: - return await repository.update_proposal_analysis(data) - - async def update_complete_proposal_analysis(self, data: dict = None): - async with self.repository() as repository: - return await repository.update_complete_proposal_analysis(data) - - async def delete_proposal_analysis(self, id: str = None, proposal_id: str = None): - async with self.repository() as repository: - return await repository.delete_proposal_analysis( - id=id, proposal_id=proposal_id - ) diff --git a/src/services/_proposal_evaluation_service.py b/src/services/_proposal_evaluation_service.py deleted file mode 100644 index b6b50099f8963cf77df4ddc4ab3e8a478ea3ed6b..0000000000000000000000000000000000000000 --- a/src/services/_proposal_evaluation_service.py +++ /dev/null @@ -1,40 +0,0 @@ -from src.repositories import ProposalEvaluationRepository -from src.models import EvaluationType - - -class ProposalEvaluationService: - def __init__(self): - self.repo = ProposalEvaluationRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_evaluations( - self, - proposal_id: str = None, - id: str = None, - evaluation_type: EvaluationType = None, - ): - async with self.repo() as repo: - return await repo.get_evaluations( - proposal_id=proposal_id, id=id, evaluation_type=evaluation_type - ) - - async def create_evaluations(self, proposal_id: str, evaluations: dict): - async with self.repo() as repo: - return await repo.craete_evaluations( - proposal_id=proposal_id, proposal_evaluations=evaluations - ) - - async def update_evaluations(self, proposal_id: str, id: str, evaluations: dict): - async with self.repo() as repo: - return await repo.update_evaluations( - proposal_id=proposal_id, id=id, evaluations=evaluations - ) - - async def delete_evaluations(self, proposal_id: str = None, id: str = None): - async with self.repo() as repo: - return await repo.delete_evaluations(proposal_id=proposal_id, id=id) diff --git a/src/services/_proposal_service.py b/src/services/_proposal_service.py deleted file mode 100644 index 6e97cddf8412c5c0923928328625bd5fb2821b07..0000000000000000000000000000000000000000 --- a/src/services/_proposal_service.py +++ /dev/null @@ -1,41 +0,0 @@ -from src.models import Proposal, ProposalStatus -from src.repositories import ProposalRepository - - -class ProposalService: - def __init__(self): - self.proposal_repository = ProposalRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_proposals( - self, proposal_id: str = None, rfp_id: str = None, status: ProposalStatus = None - ): - async with self.proposal_repository() as repository: - return await repository.get_proposals( - proposal_id=proposal_id, rfp_id=rfp_id, status=status - ) - - async def get_proposals_by_rfp_id(self, rfp_id: str): - async with self.proposal_repository() as repository: - return await repository.get_proposals_by_rfp_id(rfp_id) - - async def create_proposal(self, proposal: dict): - async with self.proposal_repository() as repository: - return await repository.create_proposal(proposal) - - async def update_proposal(self, proposal_id: str, proposal: dict): - async with self.proposal_repository() as repository: - return await repository.update_proposal( - proposal_id=proposal_id, proposal=proposal - ) - - async def delete_proposal(self, proposal_id: str = None, rfp_id: str = None): - async with self.proposal_repository() as repository: - return await repository.delete_proposal( - proposal_id=proposal_id, rfp_id=rfp_id - ) diff --git a/src/services/_rfp_service.py b/src/services/_rfp_service.py deleted file mode 100644 index cacda6d09eb25868d669d8cae21a526589864691..0000000000000000000000000000000000000000 --- a/src/services/_rfp_service.py +++ /dev/null @@ -1,29 +0,0 @@ -from src.models import RFP, RFPStatus -from src.repositories import RFPRepository - - -class RFPService: - def __init__(self): - self.rfp_repository = RFPRepository - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_rfps(self, rfp_id: str = None, status: RFPStatus = None): - async with self.rfp_repository() as repository: - return await repository.get_rfps(rfp_id=rfp_id, status=status) - - async def create_rfp(self, rfp: dict): - async with self.rfp_repository() as repository: - return await repository.create_rfp(rfp) - - async def update_rfp(self, rfp_id: str, rfp: dict): - async with self.rfp_repository() as repository: - return await repository.update_rfp(rfp_id=rfp_id, rfp=rfp) - - async def delete_rfp(self, rfp_id: str): - async with self.rfp_repository() as repository: - return await repository.delete_rfp(rfp_id) diff --git a/src/utils/__init__.py b/src/utils/__init__.py deleted file mode 100644 index 62fd8ec9d4351cbce6a179c200f01b6654e49f3b..0000000000000000000000000000000000000000 --- a/src/utils/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from ._proposal_ai_analysis_client import ProposalAIAnalysisClient -from ._score_client import ScoreClient -from ._file_client import FileClient - -__all__ = ["ProposalAIAnalysisClient", "ScoreClient", "FileClient"] -__version__ = "0.1.0" -__author__ = "Aryan Jain" diff --git a/src/utils/_file_client.py b/src/utils/_file_client.py deleted file mode 100644 index fcbba4c18624e9431e28d7a99fd0767cbbfec9a9..0000000000000000000000000000000000000000 --- a/src/utils/_file_client.py +++ /dev/null @@ -1,182 +0,0 @@ -import asyncio -import io -import json -import os -import uuid -import fitz -from openpyxl import load_workbook -import markdown2 -import subprocess -from pathlib import Path - -class FileClient: - def __init__(self): - pass - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def extract_from_pdf(self, file_bytes: io.BytesIO): - doc = fitz.open(stream=file_bytes, filetype="pdf") - - layout_data = { - "metadata": doc.metadata, - "page_count": len(doc), - "pages": [] - } - - for page_num, page in enumerate(doc): - blocks = page.get_text("dict")["blocks"] - page_content = [] - - for block in blocks: - if "lines" in block: - for line in block["lines"]: - for span in line["spans"]: - page_content.append({ - "text": span["text"], - "font": span["font"], - "size": span["size"], - "color": span.get("color", None), - "flags": span.get("flags", None) - }) - - layout_data["pages"].append({ - "page_number": page_num + 1, - "width": page.rect.width, - "height": page.rect.height, - "content": page_content - }) - - return layout_data - - async def extrcat_from_word(self, file_bytes: io.BytesIO): - data = { - "file_type": "Word Document" - } - - file_id = str(uuid.uuid4()) - work_dir = f"/files/{file_id}" - os.makedirs(work_dir, exist_ok=True) - - docx_path = os.path.join(work_dir, "input.docx") - pdf_path = os.path.join(work_dir, "input.pdf") - - try: - with open(docx_path, "wb") as f: - f.write(file_bytes.getvalue()) - - env = os.environ.copy() - env.update({ - "HOME": work_dir, - "UserInstallation": f"file://{work_dir}", - "SAL_USE_VCLPLUGIN": "svp" - }) - - cmd = [ - "libreoffice", - "--headless", - "--nologo", - "--nofirststartwizard", - "--convert-to", "pdf", - "--outdir", work_dir, - docx_path - ] - - await asyncio.to_thread( - subprocess.run, - cmd, - check=True, - capture_output=True, - env=env - ) - - with open(pdf_path, "rb") as f: - pdf_bytes = io.BytesIO(f.read()) - - data["data"] = await self.extract_from_pdf(file_bytes=pdf_bytes) - - return data - - finally: - for file_path in [docx_path, pdf_path]: - if os.path.exists(file_path): - os.remove(file_path) - - - async def extract_from_excel(self, file_bytes: io.BytesIO): - wb = load_workbook(file_bytes, data_only=True) - - sheets_data = [] - for sheet in wb.worksheets: - sheet_info = { - "sheet_name": sheet.title, - "cells": [] - } - for row in sheet.iter_rows(): - for cell in row: - if cell.value is None: - continue - - cell_info = { - "coordinate": cell.coordinate, - "value": cell.value, - } - - if cell.font: - if cell.font.name: - cell_info["font_name"] = cell.font.name - if cell.font.size: - cell_info["font_size"] = cell.font.size - if cell.font.bold: - cell_info["bold"] = True - if cell.font.italic: - cell_info["italic"] = True - if cell.font.underline: - cell_info["underline"] = True - - if cell.alignment: - if cell.alignment.horizontal: - cell_info["horizontal_align"] = cell.alignment.horizontal - if cell.alignment.vertical: - cell_info["vertical_align"] = cell.alignment.vertical - if cell.alignment.wrap_text: - cell_info["wrap_text"] = True - - if cell.fill and cell.fill.start_color and cell.fill.start_color.rgb: - color = cell.fill.start_color.rgb - if color != "00000000": - cell_info["fill_color"] = color - - cell_info = {k: v for k, v in cell_info.items() if v is not None} - sheet_info["cells"].append(cell_info) - - sheets_data.append(sheet_info) - - final_data = { - "sheets": sheets_data - } - return final_data - - async def extract_from_json(self, file_bytes: io.BytesIO): - data = json.load(file_bytes) - return data - - async def extrcat_from_md(self, file_bytes: io.BytesIO): - data = { - "file_type": "Markdown" - } - md_bytes = file_bytes.getvalue().decode("utf-8") - data["data"] = markdown2.markdown(md_bytes) - return data - - async def extract_from_txt(self, file_bytes: io.BytesIO): - data = file_bytes.getvalue().decode("utf-8") - return data - - async def extract_from_csv(self, file_bytes: io.BytesIO): - data = file_bytes.getvalue().decode("utf-8") - return data \ No newline at end of file diff --git a/src/utils/_proposal_ai_analysis_client.py b/src/utils/_proposal_ai_analysis_client.py deleted file mode 100644 index 4e18a3bd8e5daa6d8f565d7877c395385f2e6b50..0000000000000000000000000000000000000000 --- a/src/utils/_proposal_ai_analysis_client.py +++ /dev/null @@ -1,216 +0,0 @@ -from uuid import UUID -import uuid - -from sqlalchemy import select -from src.repositories import BaseRepository - -from src.models import QueryType, CreateOrUpdateType, ProposalAIAnalysis, ProposalStrength, ProposalWeakness, ProposalRejectionReason, ProposalImprovement, Category - -class ProposalAIAnalysisClient: - def __init__(self): - self.repository = BaseRepository - self.tables_mapping = { - "STRENGTHS": ProposalStrength, - "WEAKNESSES": ProposalWeakness, - "REJECTION_REASONS": ProposalRejectionReason, - "IMPROVEMENTS": ProposalImprovement, - "AI_SCORE": ProposalAIAnalysis - } - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def get_all_data(self, query_type: QueryType = QueryType.ALL, id: str = None, proposal_id: str = None, category: Category = None): - async with self.repository(model=self.tables_mapping[query_type.name]).get_session() as session: - query = select(self.tables_mapping[query_type.name]) - if id: - query = query.where(self.tables_mapping[query_type.name].id == id) - if proposal_id: - query = query.where(self.tables_mapping[query_type.name].proposal_id == proposal_id) - if category: - query = query.where(self.tables_mapping[query_type.name].category == category) - result = await session.execute(query) - all_result = result.scalars().all() - final_result_dict = [{k: v for k, v in r.__dict__.items() if not k.startswith("_")} for r in all_result] - return final_result_dict - - async def create_entry(self, query_type: QueryType = QueryType.ALL, data: dict = None): - data["id"] = str(uuid.uuid4()) - async with self.repository(model=self.tables_mapping[query_type.name]).get_session() as session: - entry = self.tables_mapping[query_type.name](**data) - session.add(entry) - await session.commit() - await session.refresh(entry) - return entry - - async def create_data(self, query_type: QueryType = QueryType.ALL, create_or_update_type: CreateOrUpdateType = CreateOrUpdateType.ALL, data: dict = None): - if create_or_update_type == CreateOrUpdateType.ID: - result = await self.create_entry(query_type=query_type, data=data) - return await self.get_all_data(query_type=query_type, id=result.id) - else: - for d in data: - if d != "proposal_id" and d != "category": - for point in data[d]: - entry_data = {"proposal_id": data["proposal_id"], "category": data["category"], d: point} - await self.create_entry(query_type=query_type, data=entry_data) - return await self.get_all_data(query_type=query_type, proposal_id=data["proposal_id"], category=data["category"]) - - async def update_data(self, query_type: QueryType = QueryType.ALL, create_or_update_type: CreateOrUpdateType = CreateOrUpdateType.ALL, data: dict = None): - if query_type == QueryType.AI_SCORE: - if create_or_update_type == CreateOrUpdateType.ID: - async with self.repository(model=self.tables_mapping[query_type.name]).get_session() as session: - result = await session.execute(select(self.tables_mapping[query_type.name]).where(self.tables_mapping[query_type.name].id == data["id"])) - all_result = result.scalars().first() - for key, value in data.items(): - setattr(all_result, key, value) - await session.commit() - await session.refresh(all_result) - return await self.get_all_data(query_type=query_type, id=data["id"]) - else: - async with self.repository(model=self.tables_mapping[query_type.name]).get_session() as session: - result = await session.execute(select(self.tables_mapping[query_type.name]).where(self.tables_mapping[query_type.name].proposal_id == data["proposal_id"])) - all_result = result.scalars().first() - for key, value in data.items(): - setattr(all_result, key, value) - await session.commit() - await session.refresh(all_result) - return await self.get_all_data(query_type=query_type, proposal_id=data["proposal_id"]) - else: - if create_or_update_type == CreateOrUpdateType.ID: - async with self.repository(model=self.tables_mapping[query_type.name]).get_session() as session: - result = await session.execute(select(self.tables_mapping[query_type.name]).where(self.tables_mapping[query_type.name].id == data["id"])) - all_result = result.scalars().all() - for r in all_result: - for key, value in data.items(): - setattr(r, key, value) - await session.commit() - await session.refresh(r) - return await self.get_all_data(query_type=query_type, id=data["id"]) - else: - await self.delete_data(query_type=query_type, proposal_id=data["proposal_id"], category=data["category"]) - return await self.create_data(query_type=query_type, create_or_update_type=create_or_update_type, data=data) - - async def delete_data(self, query_type: QueryType = QueryType.ALL, id: str = None, proposal_id: str = None, category: Category = None): - async with self.repository(model=self.tables_mapping[query_type.name]).get_session() as session: - query = select(self.tables_mapping[query_type.name]) - if id: - query = query.where(self.tables_mapping[query_type.name].id == id) - if proposal_id: - query = query.where(self.tables_mapping[query_type.name].proposal_id == proposal_id) - if category: - query = query.where(self.tables_mapping[query_type.name].category == category) - result = await session.execute(query) - all_result = result.scalars().all() - for r in all_result: - await session.delete(r) - await session.commit() - return True - - async def get_complete_analysis(self, proposal_id: str = None): - analysis = { - "ai_score": 0, - "technical": { - "strengths": [], - "weaknesses": [], - "rejection_reasons": [], - "improvements": [] - }, - "management": { - "strengths": [], - "weaknesses": [], - "rejection_reasons": [], - "improvements": [] - }, - "past_performance": { - "strengths": [], - "weaknesses": [], - "rejection_reasons": [], - "improvements": [] - }, - "price": { - "strengths": [], - "weaknesses": [], - "rejection_reasons": [], - "improvements": [] - } - } - result = await self.get_all_data(query_type=QueryType.AI_SCORE, proposal_id=proposal_id) - analysis["ai_score"] = result[0]["ai_score"] - for category in Category: - strengths = await self.get_all_data(query_type=QueryType.STRENGTHS, proposal_id=proposal_id, category=category) - analysis[category.value.lower()]["strengths"] = [s["strength_point"] for s in strengths] if strengths else [] - weaknesses = await self.get_all_data(query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, category=category) - analysis[category.value.lower()]["weaknesses"] = [w["weakness_point"] for w in weaknesses] if weaknesses else [] - rejection_reason = await self.get_all_data(query_type=QueryType.REJECTION_REASONS, proposal_id=proposal_id, category=category) - analysis[category.value.lower()]["rejection_reasons"] = [r["reason"] for r in rejection_reason] if rejection_reason else [] - improvements = await self.get_all_data(query_type=QueryType.IMPROVEMENTS, proposal_id=proposal_id, category=category) - analysis[category.value.lower()]["improvements"] = [i["improvement_point"] for i in improvements] if improvements else [] - # strengths = await self.get_all_data(query_type=QueryType.STRENGTHS, proposal_id=proposal_id) - # analysis["strengths"] = [s["strength_point"] for s in strengths] - # weakness = await self.get_all_data(query_type=QueryType.WEAKNESSES, proposal_id=proposal_id) - # analysis["weaknesses"] = [w["weakness_point"] for w in weakness] - # rejection_reason = await self.get_all_data(query_type=QueryType.REJECTION_REASONS, proposal_id=proposal_id) - # analysis["rejection_reasons"] = [r["reason"] for r in rejection_reason] - # improvements = await self.get_all_data(query_type=QueryType.IMPROVEMENTS, proposal_id=proposal_id) - # analysis["improvements"] = [i["improvement_point"] for i in improvements] - return analysis - - async def create_complete_analysis(self, data: dict = None): - if "ai_score" in data: - await self.create_data(query_type=QueryType.AI_SCORE, create_or_update_type=CreateOrUpdateType.ID, data={"proposal_id": data["proposal_id"], "ai_score": data["ai_score"]}) - for category in Category: - if "strengths" in data[category.value.lower()]: - await self.create_data(query_type=QueryType.STRENGTHS, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "category": category, "strength_point": data[category.value.lower()]["strengths"]}) - if "weaknesses" in data[category.value.lower()]: - await self.create_data(query_type=QueryType.WEAKNESSES, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "category": category, "weakness_point": data[category.value.lower()]["weaknesses"]}) - if "rejection_reasons" in data[category.value.lower()]: - await self.create_data(query_type=QueryType.REJECTION_REASONS, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "category": category, "reason": data[category.value.lower()]["rejection_reasons"]}) - if "improvements" in data[category.value.lower()]: - await self.create_data(query_type=QueryType.IMPROVEMENTS, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "category": category, "improvement_point": data[category.value.lower()]["improvements"]}) - # if "strengths" in data: - # await self.create_data(query_type=QueryType.STRENGTHS, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "strength_point": data["strengths"]}) - # if "weaknesses" in data: - # await self.create_data(query_type=QueryType.WEAKNESSES, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "weakness_point": data["weaknesses"]}) - # if "rejection_reasons" in data: - # await self.create_data(query_type=QueryType.REJECTION_REASONS, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "reason": data["rejection_reasons"]}) - # if "improvements" in data: - # await self.create_data(query_type=QueryType.IMPROVEMENTS, create_or_update_type=CreateOrUpdateType.ALL, data={"proposal_id": data["proposal_id"], "improvement_point": data["improvements"]}) - return await self.get_complete_analysis(proposal_id=data["proposal_id"]) - - async def delete_complete_analysis(self, proposal_id: str = None): - await self.delete_data(query_type=QueryType.AI_SCORE, proposal_id=proposal_id) - await self.delete_data(query_type=QueryType.STRENGTHS, proposal_id=proposal_id) - await self.delete_data(query_type=QueryType.WEAKNESSES, proposal_id=proposal_id) - await self.delete_data(query_type=QueryType.REJECTION_REASONS, proposal_id=proposal_id) - await self.delete_data(query_type=QueryType.IMPROVEMENTS, proposal_id=proposal_id) - return True - - async def update_complete_analysis(self, data: dict = None): - old_data = await self.get_complete_analysis(proposal_id=data["proposal_id"]) - if "ai_score" in data: - old_data["ai_score"] = data["ai_score"] - for category in Category: - if category.value.lower() not in data: - continue - if "strengths" in data[category.value.lower()]: - old_data[category.value.lower()]["strengths"] = data[category.value.lower()]["strengths"] - if "weaknesses" in data[category.value.lower()]: - old_data[category.value.lower()]["weaknesses"] = data[category.value.lower()]["weaknesses"] - if "rejection_reasons" in data[category.value.lower()]: - old_data[category.value.lower()]["rejection_reasons"] = data[category.value.lower()]["rejection_reasons"] - if "improvements" in data[category.value.lower()]: - old_data[category.value.lower()]["improvements"] = data[category.value.lower()]["improvements"] - # if "strengths" in data: - # old_data["strengths"] = data["strengths"] - # if "weaknesses" in data: - # old_data["weaknesses"] = data["weaknesses"] - # if "rejection_reasons" in data: - # old_data["rejection_reasons"] = data["rejection_reasons"] - # if "improvements" in data: - # old_data["improvements"] = data["improvements"] - await self.delete_complete_analysis(proposal_id=data["proposal_id"]) - old_data["proposal_id"] = data["proposal_id"] - return await self.create_complete_analysis(data=old_data) \ No newline at end of file diff --git a/src/utils/_score_client.py b/src/utils/_score_client.py deleted file mode 100644 index 5d3a5d4acecd7c191c6d4dab3ab06b01a454ec08..0000000000000000000000000000000000000000 --- a/src/utils/_score_client.py +++ /dev/null @@ -1,202 +0,0 @@ -from uuid import UUID -import uuid - -from sqlalchemy import select -from src.repositories import BaseRepository - -from src.models import ( - AnalysisType, - Analysis, - Proposal, - ComparativeWeights, - Weights, - Evaluations, - EvaluationType, - AnalysisStatus, -) - - -class ScoreClient: - def __init__(self): - self.repository = BaseRepository - self.weighted_scores = {"NONE": 0, "LOW": 1, "MEDIUM": 3, "HIGH": 7} - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_value, traceback): - pass - - async def update_ai_score( - self, evaluation_id: str, analysis_score_type: str = None - ): - async with self.repository(model=Evaluations).get_session() as session: - query = select(Evaluations).where(Evaluations.id == evaluation_id) - result = await session.execute(query) - evaluation = result.scalars().first() - proposal_id = evaluation.proposal_id - - async with self.repository(model=Proposal).get_session() as session: - query = select(Proposal).where(Proposal.id == proposal_id) - result = await session.execute(query) - proposal = result.scalars().first() - rfp_id = proposal.rfp_id - - async with self.repository(model=Analysis).get_session() as session: - query = select(Analysis).where(Analysis.evaluation_id == evaluation_id) - result = await session.execute(query) - results = result.scalars().all() - deficiency = [ - result.insights - for result in results - if result.analysis_type == AnalysisType.DEFICIENCIES - ] - weaknesses = [ - result.insights - for result in results - if result.analysis_type == AnalysisType.WEAKNESSES - ] - strengths = [ - result.insights - for result in results - if result.analysis_type == AnalysisType.STRENGTHS - ] - if analysis_score_type != "AI": - deficiency = [ - result.insights - for result in results - if result.analysis_type == AnalysisType.DEFICIENCIES - and result.status != AnalysisStatus.REJECTED - ] - weaknesses = [ - result.insights - for result in results - if result.analysis_type == AnalysisType.WEAKNESSES - and result.status != AnalysisStatus.REJECTED - ] - strengths = [ - result.insights - for result in results - if result.analysis_type == AnalysisType.STRENGTHS - and result.status != AnalysisStatus.REJECTED - ] - start_score = 0 if deficiency else 100 - - async with self.repository(model=ComparativeWeights).get_session() as session: - query = select(ComparativeWeights).where( - ComparativeWeights.rfp_id == rfp_id - ) - result = await session.execute(query) - comparative_weights = result.scalars().first() - - start_score += len(strengths) * comparative_weights.strengths_weight - start_score -= len(weaknesses) * comparative_weights.weaknesses_weight - - async with self.repository(model=Evaluations).get_session() as session: - query = select(Evaluations).where(Evaluations.id == evaluation_id) - result = await session.execute(query) - evaluation = result.scalars().first() - if analysis_score_type == "AI": - evaluation.ai_score = start_score - evaluation.adjusted_score = start_score - else: - evaluation.adjusted_score = start_score - await session.commit() - await session.refresh(evaluation) - query = select(Evaluations).where(Evaluations.proposal_id == proposal_id) - result = await session.execute(query) - evaluations = result.scalars().all() - - technical_ai_score = [ - evaluation.ai_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.TECHNICAL - and evaluation.ai_score is not None - ] - management_ai_score = [ - evaluation.ai_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.MANAGEMENT - and evaluation.ai_score is not None - ] - past_performance_ai_score = [ - evaluation.ai_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.PAST_PERFORMANCE - and evaluation.ai_score is not None - ] - price_ai_score = [ - evaluation.ai_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.PRICE - and evaluation.ai_score is not None - ] - - if analysis_score_type != "AI": - technical_ai_score = [ - evaluation.adjusted_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.TECHNICAL - and evaluation.adjusted_score is not None - ] - management_ai_score = [ - evaluation.adjusted_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.MANAGEMENT - and evaluation.adjusted_score is not None - ] - past_performance_ai_score = [ - evaluation.adjusted_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.PAST_PERFORMANCE - and evaluation.adjusted_score is not None - ] - price_ai_score = [ - evaluation.adjusted_score - for evaluation in evaluations - if evaluation.evaluation_type == EvaluationType.PRICE - and evaluation.adjusted_score is not None - ] - - technical_ai_score = technical_ai_score[0] if technical_ai_score else 0 - management_ai_score = management_ai_score[0] if management_ai_score else 0 - past_performance_ai_score = ( - past_performance_ai_score[0] if past_performance_ai_score else 0 - ) - price_ai_score = price_ai_score[0] if price_ai_score else 0 - - technical_weight = self.weighted_scores[ - comparative_weights.technical_weight.name - ] - management_weight = self.weighted_scores[ - comparative_weights.management_weight.name - ] - past_performance_weight = self.weighted_scores[ - comparative_weights.past_performance_weight.name - ] - price_weight = self.weighted_scores[comparative_weights.price_weight.name] - - ai_score = ( - technical_ai_score * technical_weight - + management_ai_score * management_weight - + past_performance_ai_score * past_performance_weight - + price_ai_score * price_weight - ) - ai_score /= ( - technical_weight - + management_weight - + past_performance_weight - + price_weight - ) - - async with self.repository(model=Proposal).get_session() as session: - query = select(Proposal).where(Proposal.id == proposal_id) - result = await session.execute(query) - evaluation = result.scalars().first() - if analysis_score_type == "AI": - evaluation.ai_score = ai_score - evaluation.final_score = ai_score - else: - evaluation.final_score = ai_score - await session.commit() - await session.refresh(evaluation) diff --git a/test.py b/test.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000