merge to main

#1
This view is limited to 50 files because it contains too many changes. See the raw diff here.
Files changed (50) hide show
  1. .env.example +0 -1
  2. .gitignore +0 -140
  3. Dockerfile +0 -54
  4. alembic.ini +0 -141
  5. alembic/README +0 -1
  6. alembic/env.py +0 -95
  7. alembic/script.py.mako +0 -28
  8. alembic/versions/0402a665d940_update_rfp_table.py +0 -28
  9. alembic/versions/11d43047d470_initial_tables.py +0 -118
  10. alembic/versions/3ba37b484f31_update_enum_type.py +0 -56
  11. alembic/versions/718e96a6869a_update_tables.py +0 -125
  12. alembic/versions/7fae52a30825_update_rfp_table.py +0 -31
  13. alembic/versions/7fb86c16841c_add_flag_in_rfp_table.py +0 -36
  14. alembic/versions/80b53babd4e6_update_flag_column_name.py +0 -36
  15. alembic/versions/a3bbab758ddb_categorize_the_analysis.py +0 -91
  16. alembic/versions/b5d2f8b83d7e_add_flag_and_status.py +0 -41
  17. alembic/versions/c70330d677c0_add_evaluation_criteria.py +0 -38
  18. alembic/versions/e825902b2e8a_alter_tables.py +0 -49
  19. poetry.lock +0 -0
  20. pyproject.toml +0 -32
  21. src/__init__.py +0 -15
  22. src/app.py +0 -65
  23. src/config/__init__.py +0 -9
  24. src/config/_databse_config.py +0 -37
  25. src/config/_logger.py +0 -8
  26. src/controllers/__init__.py +0 -28
  27. src/controllers/_analysis_conteoller.py +0 -249
  28. src/controllers/_comparative_weight_controller.py +0 -237
  29. src/controllers/_evaluation_controller.py +0 -299
  30. src/controllers/_file_controller.py +0 -34
  31. src/controllers/_letter_controller.py +0 -263
  32. src/controllers/_proposal_ai_analysis_controller.py +0 -1783
  33. src/controllers/_proposal_controller.py +0 -338
  34. src/controllers/_proposal_detailed_analysis_controller.py +0 -291
  35. src/controllers/_proposal_evaluation_controller.py +0 -209
  36. src/controllers/_rfp_controller.py +0 -170
  37. src/models/__init__.py +0 -50
  38. src/models/_analysis.py +0 -42
  39. src/models/_base.py +0 -3
  40. src/models/_comparitive_weights.py +0 -47
  41. src/models/_evaluation.py +0 -34
  42. src/models/_letters.py +0 -36
  43. src/models/_proposal.py +0 -46
  44. src/models/_proposal_ai_analysis.py +0 -103
  45. src/models/_proposal_detailed_analysis.py +0 -39
  46. src/models/_proposal_evaluation.py +0 -40
  47. src/models/_proposal_query_models.py +0 -13
  48. src/models/_rfp.py +0 -47
  49. src/repositories/__init__.py +0 -23
  50. src/repositories/_analysis_repository.py +0 -99
.env.example DELETED
@@ -1 +0,0 @@
1
- SQLALCHEMY_DATABASE_URI=sqlite+aiosqlite:///:memory:
 
 
.gitignore DELETED
@@ -1,140 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- share/python-wheels/
24
- *.egg-info/
25
- .installed.cfg
26
- *.egg
27
- MANIFEST
28
-
29
- # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
- *.manifest
33
- *.spec
34
-
35
- # Installer logs
36
- pip-log.txt
37
- pip-delete-this-directory.txt
38
-
39
- # Unit test / coverage reports
40
- htmlcov/
41
- .tox/
42
- .nox/
43
- .coverage
44
- .coverage.*
45
- .cache
46
- nosetests.xml
47
- coverage.xml
48
- *.cover
49
- *.py,cover
50
- .hypothesis/
51
- .pytest_cache/
52
- cover/
53
-
54
- # Translations
55
- *.mo
56
- *.pot
57
-
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
- # Flask stuff:
65
- instance/
66
- .webassets-cache
67
-
68
- # Scrapy stuff:
69
- .scrapy
70
-
71
- # Sphinx documentation
72
- docs/_build/
73
-
74
- # PyBuilder
75
- .pybuilder/
76
- target/
77
-
78
- # Jupyter Notebook
79
- .ipynb_checkpoints
80
-
81
- # IPython
82
- profile_default/
83
- ipython_config.py
84
-
85
- # pyenv
86
- # For a library or package, you might want to ignore these files since the code is
87
- # intended to run in multiple environments; otherwise, check them in:
88
- # .python-version
89
-
90
- # pipenv
91
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
- # install all needed dependencies.
95
- #Pipfile.lock
96
-
97
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98
- __pypackages__/
99
-
100
- # Celery stuff
101
- celerybeat-schedule
102
- celerybeat.pid
103
-
104
- # SageMath parsed files
105
- *.sage.py
106
-
107
- # Environments
108
- .env
109
- .venv
110
- env/
111
- venv/
112
- ENV/
113
- env.bak/
114
- venv.bak/
115
-
116
- # Spyder project settings
117
- .spyderproject
118
- .spyproject
119
-
120
- # Rope project settings
121
- .ropeproject
122
-
123
- # mkdocs documentation
124
- /site
125
-
126
- # mypy
127
- .mypy_cache/
128
- .dmypy.json
129
- dmypy.json
130
-
131
- # Pyre type checker
132
- .pyre/
133
-
134
- # pytype static type analyzer
135
- .pytype/
136
-
137
- # Cython debug symbols
138
- cython_debug/
139
-
140
- .vscode
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile DELETED
@@ -1,54 +0,0 @@
1
- # Use official Python 3.12 slim image
2
- FROM python:3.12-slim
3
-
4
- # Set environment variables
5
- ENV SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://avnadmin:AVNS_W4PzkCRVJw0ASHFYc8i@pg-5cdc7c9-aryanjain19082002-7775.j.aivencloud.com:25607/defaultdb
6
- ENV PYTHONUNBUFFERED=1
7
- ENV POETRY_VERSION=1.8.2
8
-
9
- # Set work directory
10
- WORKDIR /app
11
-
12
- # Install system dependencies
13
- RUN apt-get update && apt-get install -y \
14
- curl \
15
- build-essential \
16
- libreoffice \
17
- default-jre-headless \
18
- fonts-dejavu \
19
- fonts-liberation \
20
- fontconfig \
21
- xvfb \
22
- dbus-x11 \
23
- && apt-get clean \
24
- && rm -rf /var/lib/apt/lists/*
25
-
26
-
27
- # Install Poetry
28
- RUN curl -sSL https://install.python-poetry.org | python3 -
29
- ENV PATH="/root/.local/bin:$PATH"
30
-
31
- # Copy only dependency files first to leverage Docker cache
32
- COPY pyproject.toml poetry.lock* /app/
33
-
34
- # Install dependencies (without creating a virtualenv)
35
- RUN poetry config virtualenvs.create false \
36
- && poetry install --no-interaction --no-ansi
37
-
38
- RUN mkdir -p /tmp/.config/libreoffice && \
39
- chmod 755 /tmp/.config/libreoffice
40
-
41
- RUN mkdir -p /files && chmod 777 /files
42
-
43
- ENV HOME=/tmp \
44
- TMPDIR=/tmp \
45
- JAVA_HOME=/usr/lib/jvm/default-java
46
-
47
- # Copy the entire project
48
- COPY . /app/
49
-
50
- # Expose the port Uvicorn will run on
51
- EXPOSE 7860
52
-
53
- # Run the FastAPI application
54
- CMD ["uvicorn", "src.app:app", "--reload", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic.ini DELETED
@@ -1,141 +0,0 @@
1
- # A generic, single database configuration.
2
-
3
- [alembic]
4
- # path to migration scripts.
5
- # this is typically a path given in POSIX (e.g. forward slashes)
6
- # format, relative to the token %(here)s which refers to the location of this
7
- # ini file
8
- script_location = %(here)s/alembic
9
-
10
- # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
11
- # Uncomment the line below if you want the files to be prepended with date and time
12
- # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
13
- # for all available tokens
14
- # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
15
-
16
- # sys.path path, will be prepended to sys.path if present.
17
- # defaults to the current working directory. for multiple paths, the path separator
18
- # is defined by "path_separator" below.
19
- prepend_sys_path = .
20
-
21
- # timezone to use when rendering the date within the migration file
22
- # as well as the filename.
23
- # If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
24
- # Any required deps can installed by adding `alembic[tz]` to the pip requirements
25
- # string value is passed to ZoneInfo()
26
- # leave blank for localtime
27
- # timezone =
28
-
29
- # max length of characters to apply to the "slug" field
30
- # truncate_slug_length = 40
31
-
32
- # set to 'true' to run the environment during
33
- # the 'revision' command, regardless of autogenerate
34
- # revision_environment = false
35
-
36
- # set to 'true' to allow .pyc and .pyo files without
37
- # a source .py file to be detected as revisions in the
38
- # versions/ directory
39
- # sourceless = false
40
-
41
- # version location specification; This defaults
42
- # to <script_location>/versions. When using multiple version
43
- # directories, initial revisions must be specified with --version-path.
44
- # The path separator used here should be the separator specified by "path_separator"
45
- # below.
46
- # version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
47
-
48
- # path_separator; This indicates what character is used to split lists of file
49
- # paths, including version_locations and prepend_sys_path within configparser
50
- # files such as alembic.ini.
51
- # The default rendered in new alembic.ini files is "os", which uses os.pathsep
52
- # to provide os-dependent path splitting.
53
- #
54
- # Note that in order to support legacy alembic.ini files, this default does NOT
55
- # take place if path_separator is not present in alembic.ini. If this
56
- # option is omitted entirely, fallback logic is as follows:
57
- #
58
- # 1. Parsing of the version_locations option falls back to using the legacy
59
- # "version_path_separator" key, which if absent then falls back to the legacy
60
- # behavior of splitting on spaces and/or commas.
61
- # 2. Parsing of the prepend_sys_path option falls back to the legacy
62
- # behavior of splitting on spaces, commas, or colons.
63
- #
64
- # Valid values for path_separator are:
65
- #
66
- # path_separator = :
67
- # path_separator = ;
68
- # path_separator = space
69
- # path_separator = newline
70
- #
71
- # Use os.pathsep. Default configuration used for new projects.
72
- path_separator = os
73
-
74
-
75
- # set to 'true' to search source files recursively
76
- # in each "version_locations" directory
77
- # new in Alembic version 1.10
78
- # recursive_version_locations = false
79
-
80
- # the output encoding used when revision files
81
- # are written from script.py.mako
82
- # output_encoding = utf-8
83
-
84
- # database URL. This is consumed by the user-maintained env.py script only.
85
- # other means of configuring database URLs may be customized within the env.py
86
- # file.
87
- sqlalchemy.url = driver://user:pass@host/db
88
-
89
-
90
- [post_write_hooks]
91
- # post_write_hooks defines scripts or Python functions that are run
92
- # on newly generated revision scripts. See the documentation for further
93
- # detail and examples
94
-
95
- # format using "black" - use the console_scripts runner, against the "black" entrypoint
96
- # hooks = black
97
- # black.type = console_scripts
98
- # black.entrypoint = black
99
- # black.options = -l 79 REVISION_SCRIPT_FILENAME
100
-
101
- # lint with attempts to fix using "ruff" - use the exec runner, execute a binary
102
- # hooks = ruff
103
- # ruff.type = exec
104
- # ruff.executable = %(here)s/.venv/bin/ruff
105
- # ruff.options = check --fix REVISION_SCRIPT_FILENAME
106
-
107
- # Logging configuration. This is also consumed by the user-maintained
108
- # env.py script only.
109
- [loggers]
110
- keys = root,sqlalchemy,alembic
111
-
112
- [handlers]
113
- keys = console
114
-
115
- [formatters]
116
- keys = generic
117
-
118
- [logger_root]
119
- level = WARNING
120
- handlers = console
121
- qualname =
122
-
123
- [logger_sqlalchemy]
124
- level = WARNING
125
- handlers =
126
- qualname = sqlalchemy.engine
127
-
128
- [logger_alembic]
129
- level = INFO
130
- handlers =
131
- qualname = alembic
132
-
133
- [handler_console]
134
- class = StreamHandler
135
- args = (sys.stderr,)
136
- level = NOTSET
137
- formatter = generic
138
-
139
- [formatter_generic]
140
- format = %(levelname)-5.5s [%(name)s] %(message)s
141
- datefmt = %H:%M:%S
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/README DELETED
@@ -1 +0,0 @@
1
- Generic single-database configuration with an async dbapi.
 
 
alembic/env.py DELETED
@@ -1,95 +0,0 @@
1
- import asyncio
2
- from logging.config import fileConfig
3
-
4
- from alembic import context
5
- from sqlalchemy import pool
6
- from sqlalchemy.engine import Connection
7
- from sqlalchemy.ext.asyncio import async_engine_from_config
8
-
9
- from src.models import *
10
-
11
- # this is the Alembic Config object, which provides
12
- # access to the values within the .ini file in use.
13
- config = context.config
14
-
15
- # Interpret the config file for Python logging.
16
- # This line sets up loggers basically.
17
- if config.config_file_name is not None:
18
- fileConfig(config.config_file_name)
19
-
20
- # add your model's MetaData object here
21
- # for 'autogenerate' support
22
- # from myapp import mymodel
23
- # target_metadata = mymodel.Base.metadata
24
- target_metadata = Base.metadata
25
-
26
- # other values from the config, defined by the needs of env.py,
27
- # can be acquired:
28
- # my_important_option = config.get_main_option("my_important_option")
29
- # ... etc.
30
-
31
-
32
- def run_migrations_offline() -> None:
33
- """Run migrations in 'offline' mode.
34
-
35
- This configures the context with just a URL
36
- and not an Engine, though an Engine is acceptable
37
- here as well. By skipping the Engine creation
38
- we don't even need a DBAPI to be available.
39
-
40
- Calls to context.execute() here emit the given string to the
41
- script output.
42
-
43
- """
44
- url = config.get_main_option("sqlalchemy.url")
45
- context.configure(
46
- url=url,
47
- target_metadata=target_metadata,
48
- literal_binds=True,
49
- dialect_opts={"paramstyle": "named"},
50
- )
51
-
52
- with context.begin_transaction():
53
- context.run_migrations()
54
-
55
-
56
- def do_run_migrations(connection: Connection) -> None:
57
- context.configure(connection=connection, target_metadata=target_metadata)
58
-
59
- with context.begin_transaction():
60
- context.run_migrations()
61
-
62
-
63
- async def run_async_migrations() -> None:
64
- """In this scenario we need to create an Engine
65
- and associate a connection with the context.
66
-
67
- """
68
-
69
- connectable = async_engine_from_config(
70
- config.get_section(config.config_ini_section, {}),
71
- prefix="sqlalchemy.",
72
- poolclass=pool.NullPool,
73
- )
74
-
75
- async with connectable.connect() as connection:
76
- await connection.run_sync(do_run_migrations)
77
-
78
- await connectable.dispose()
79
-
80
-
81
- def run_migrations_online() -> None:
82
- """Run migrations in 'online' mode."""
83
-
84
- connectable = config.attributes.get("connection", None)
85
-
86
- if connectable is None:
87
- asyncio.run(run_async_migrations())
88
- else:
89
- do_run_migrations(connectable)
90
-
91
-
92
- if context.is_offline_mode():
93
- run_migrations_offline()
94
- else:
95
- run_migrations_online()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/script.py.mako DELETED
@@ -1,28 +0,0 @@
1
- """${message}
2
-
3
- Revision ID: ${up_revision}
4
- Revises: ${down_revision | comma,n}
5
- Create Date: ${create_date}
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
- ${imports if imports else ""}
13
-
14
- # revision identifiers, used by Alembic.
15
- revision: str = ${repr(up_revision)}
16
- down_revision: Union[str, None] = ${repr(down_revision)}
17
- branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
- depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
-
20
-
21
- def upgrade() -> None:
22
- """Upgrade schema."""
23
- ${upgrades if upgrades else "pass"}
24
-
25
-
26
- def downgrade() -> None:
27
- """Downgrade schema."""
28
- ${downgrades if downgrades else "pass"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/0402a665d940_update_rfp_table.py DELETED
@@ -1,28 +0,0 @@
1
- """update rfp table
2
-
3
- Revision ID: 0402a665d940
4
- Revises: c70330d677c0
5
- Create Date: 2025-06-04 10:36:51.925465
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
-
14
- # revision identifiers, used by Alembic.
15
- revision: str = '0402a665d940'
16
- down_revision: Union[str, None] = 'c70330d677c0'
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- """Upgrade schema."""
23
- op.add_column("rfps", sa.Column("target_award_date", sa.DateTime(), nullable=True))
24
-
25
-
26
- def downgrade() -> None:
27
- """Downgrade schema."""
28
- op.drop_column("rfps", "target_award_date")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/11d43047d470_initial_tables.py DELETED
@@ -1,118 +0,0 @@
1
- """Initial tables
2
-
3
- Revision ID: 11d43047d470
4
- Revises:
5
- Create Date: 2025-05-29 13:59:12.209373
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
- from sqlalchemy.dialects import postgresql
14
-
15
-
16
- # revision identifiers, used by Alembic.
17
- revision: str = '11d43047d470'
18
- down_revision: Union[str, None] = None
19
- branch_labels: Union[str, Sequence[str], None] = None
20
- depends_on: Union[str, Sequence[str], None] = None
21
-
22
-
23
- def upgrade() -> None:
24
- """Upgrade schema."""
25
- # ### commands auto generated by Alembic - please adjust! ###
26
- op.create_table(
27
- 'rfps',
28
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
29
- sa.Column('rfp_number', sa.String(), nullable=True),
30
- sa.Column('received_proposals', sa.Integer(), nullable=True),
31
- sa.Column('evaluated_count', sa.Integer(), nullable=True),
32
- sa.Column('awaiting_evaluation', sa.Integer(), nullable=True),
33
- sa.Column('created_at', sa.DateTime(), nullable=False),
34
- sa.Column('updated_at', sa.DateTime(), nullable=False),
35
- )
36
-
37
- op.create_table(
38
- 'proposals',
39
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
40
- sa.Column('rfp_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('rfps.id', ondelete="CASCADE"), nullable=False),
41
- sa.Column('name', sa.String(), nullable=True),
42
- sa.Column('status', sa.Enum('APPROVED', 'EVALUATED', 'IN_REVIEW', name='proposalstatus'), nullable=False),
43
- sa.Column('created_at', sa.DateTime(), nullable=False),
44
- sa.Column('updated_at', sa.DateTime(), nullable=False),
45
- )
46
-
47
- op.create_table(
48
- 'proposal_ai_analyses',
49
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
50
- sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False),
51
- sa.Column('ai_score', sa.Float(), nullable=True),
52
- sa.Column('created_at', sa.DateTime(), nullable=False),
53
- sa.Column('updated_at', sa.DateTime(), nullable=False),
54
- )
55
-
56
- op.create_table(
57
- 'proposal_strengths',
58
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
59
- sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False),
60
- sa.Column('strength_point', sa.String(), nullable=True),
61
- sa.Column('created_at', sa.DateTime(), nullable=False),
62
- sa.Column('updated_at', sa.DateTime(), nullable=False),
63
- )
64
-
65
- op.create_table(
66
- 'proposal_rejection_reasons',
67
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
68
- sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False),
69
- sa.Column('reason', sa.String(), nullable=True),
70
- sa.Column('created_at', sa.DateTime(), nullable=False),
71
- sa.Column('updated_at', sa.DateTime(), nullable=False),
72
- )
73
-
74
- op.create_table(
75
- 'proposal_weaknesses',
76
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
77
- sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False),
78
- sa.Column('weakness_point', sa.String(), nullable=True),
79
- sa.Column('created_at', sa.DateTime(), nullable=False),
80
- sa.Column('updated_at', sa.DateTime(), nullable=False),
81
- )
82
-
83
- op.create_table(
84
- 'proposal_improvements',
85
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
86
- sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False),
87
- sa.Column('improvement_point', sa.String(), nullable=True),
88
- sa.Column('created_at', sa.DateTime(), nullable=False),
89
- sa.Column('updated_at', sa.DateTime(), nullable=False),
90
- )
91
-
92
- op.create_table(
93
- 'proposal_detail_analyses',
94
- sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
95
- sa.Column('proposal_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('proposals.id', ondelete="CASCADE"), nullable=False),
96
- sa.Column('ai_assessment_result', sa.String(), nullable=True),
97
- sa.Column('confidence', sa.Float(), nullable=True),
98
- sa.Column('operation_criteria', sa.Enum(
99
- 'MISSION_ALIGNMENT', 'TECHNICAL_FEASIBILITY', 'COST_EFFICIENCY', 'RISK_ASSESSMENT',
100
- name='operationcriteria'), nullable=False),
101
- sa.Column('created_at', sa.DateTime(), nullable=False),
102
- sa.Column('updated_at', sa.DateTime(), nullable=False),
103
- )
104
- # ### end Alembic commands ###
105
-
106
-
107
- def downgrade() -> None:
108
- """Downgrade schema."""
109
- # ### commands auto generated by Alembic - please adjust! ###
110
- op.drop_table('proposal_detail_analyses')
111
- op.drop_table('proposal_improvements')
112
- op.drop_table('proposal_weaknesses')
113
- op.drop_table('proposal_rejection_reasons')
114
- op.drop_table('proposal_strengths')
115
- op.drop_table('proposal_ai_analyses')
116
- op.drop_table('proposals')
117
- op.drop_table('rfps')
118
- # ### end Alembic commands ###
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/3ba37b484f31_update_enum_type.py DELETED
@@ -1,56 +0,0 @@
1
- """update enum type
2
-
3
- Revision ID: 3ba37b484f31
4
- Revises: 718e96a6869a
5
- Create Date: 2025-06-05 10:04:03.238833
6
-
7
- """
8
-
9
- from typing import Sequence, Union
10
-
11
- from alembic import op
12
- import sqlalchemy as sa
13
-
14
-
15
- # revision identifiers, used by Alembic.
16
- revision: str = "3ba37b484f31"
17
- down_revision: Union[str, None] = "718e96a6869a"
18
- branch_labels: Union[str, Sequence[str], None] = None
19
- depends_on: Union[str, Sequence[str], None] = None
20
-
21
-
22
- def upgrade() -> None:
23
- """Upgrade schema."""
24
- op.execute(
25
- "CREATE TYPE analysistype_new AS ENUM ('STRENGTHS', 'WEAKNESSES', 'DEFICIENCIES')"
26
- )
27
-
28
- op.execute(
29
- """
30
- ALTER TABLE analysis
31
- ALTER COLUMN analysis_type TYPE analysistype_new
32
- USING analysis_type::text::analysistype_new
33
- """
34
- )
35
-
36
- op.execute("DROP TYPE analysistype")
37
-
38
- op.execute("ALTER TYPE analysistype_new RENAME TO analysistype")
39
-
40
-
41
- def downgrade() -> None:
42
- """Downgrade schema."""
43
- op.execute(
44
- "CREATE TYPE analysistype_old AS ENUM ('STRENGHTS', 'WEAKNESSES', 'DEFICIENCIES')"
45
- )
46
-
47
- op.execute(
48
- """
49
- ALTER TABLE analysis
50
- ALTER COLUMN analysis_type TYPE analysistype_old
51
- USING analysis_type::text::analysistype_old
52
- """
53
- )
54
-
55
- op.execute("DROP TYPE analysistype")
56
- op.execute("ALTER TYPE analysistype_old RENAME TO analysistype")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/718e96a6869a_update_tables.py DELETED
@@ -1,125 +0,0 @@
1
- """update tables
2
-
3
- Revision ID: 718e96a6869a
4
- Revises: a3bbab758ddb
5
- Create Date: 2025-06-05 05:27:57.408552
6
-
7
- """
8
-
9
- from typing import Sequence, Union
10
-
11
- from alembic import op
12
- import sqlalchemy as sa
13
-
14
- from sqlalchemy.dialects import postgresql
15
-
16
- # revision identifiers, used by Alembic.
17
- revision: str = "718e96a6869a"
18
- down_revision: Union[str, None] = "a3bbab758ddb"
19
- branch_labels: Union[str, Sequence[str], None] = None
20
- depends_on: Union[str, Sequence[str], None] = None
21
-
22
-
23
- def upgrade() -> None:
24
- """Upgrade schema."""
25
- op.add_column("rfps", sa.Column("ko_name", sa.String(), nullable=True))
26
- op.add_column("rfps", sa.Column("award_type", sa.String(), nullable=True))
27
- op.add_column("rfps", sa.Column("google_drive_id", sa.String(), nullable=True))
28
-
29
- op.add_column("proposals", sa.Column("ai_score", sa.Float(), nullable=True))
30
- op.add_column("proposals", sa.Column("final_score", sa.Float(), nullable=True))
31
-
32
- op.create_table(
33
- "comparative_weights",
34
- sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
35
- sa.Column(
36
- "rfp_id",
37
- postgresql.UUID(as_uuid=True),
38
- sa.ForeignKey("rfps.id", ondelete="CASCADE"),
39
- nullable=False,
40
- unique=True,
41
- ),
42
- sa.Column(
43
- "technical_weight",
44
- sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"),
45
- nullable=False,
46
- ),
47
- sa.Column(
48
- "management_weight",
49
- sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"),
50
- nullable=False,
51
- ),
52
- sa.Column(
53
- "past_performance_weight",
54
- sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"),
55
- nullable=False,
56
- ),
57
- sa.Column(
58
- "price_weight",
59
- sa.Enum("NONE", "LOW", "MEDIUM", "HIGH", name="comparativeweights"),
60
- nullable=False,
61
- ),
62
- sa.Column("strengths_weight", sa.Float(), nullable=False, default=15),
63
- sa.Column("weaknesses_weight", sa.Float(), nullable=False),
64
- sa.Column("created_at", sa.DateTime(), nullable=False),
65
- sa.Column("updated_at", sa.DateTime(), nullable=False),
66
- )
67
-
68
- op.create_table(
69
- "evaluations",
70
- sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
71
- sa.Column(
72
- "proposal_id",
73
- postgresql.UUID(as_uuid=True),
74
- sa.ForeignKey("proposals.id", ondelete="CASCADE"),
75
- nullable=False,
76
- ),
77
- sa.Column(
78
- "evaluation_type",
79
- sa.Enum(
80
- "TECHNICAL",
81
- "MANAGEMENT",
82
- "PAST_PERFORMANCE",
83
- "PRICE",
84
- name="evaluationtype",
85
- ),
86
- nullable=False,
87
- ),
88
- sa.Column("ai_score", sa.Float(), nullable=True),
89
- sa.Column("adjusted_score", sa.Float(), nullable=True),
90
- sa.Column("created_at", sa.DateTime(), nullable=False),
91
- sa.Column("updated_at", sa.DateTime(), nullable=False),
92
- )
93
-
94
- op.create_table(
95
- "analysis",
96
- sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
97
- sa.Column(
98
- "evaluation_id",
99
- postgresql.UUID(as_uuid=True),
100
- sa.ForeignKey("evaluations.id", ondelete="CASCADE"),
101
- nullable=False,
102
- ),
103
- sa.Column("insights", sa.Text(), nullable=True),
104
- sa.Column(
105
- "analysis_type",
106
- sa.Enum("STRENGHTS", "WEAKNESSES", "DEFICIENCIES", name="analysistype"),
107
- nullable=False,
108
- ),
109
- sa.Column("created_at", sa.DateTime(), nullable=False),
110
- sa.Column("updated_at", sa.DateTime(), nullable=False),
111
- )
112
-
113
-
114
- def downgrade() -> None:
115
- """Downgrade schema."""
116
- op.drop_column("rfps", "ko_name")
117
- op.drop_column("rfps", "award_type")
118
- op.drop_column("rfps", "google_drive_id")
119
-
120
- op.drop_column("proposals", "ai_score")
121
- op.drop_column("proposals", "final_score")
122
-
123
- op.drop_table("comparative_weights")
124
- op.drop_table("evaluations")
125
- op.drop_table("analysis")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/7fae52a30825_update_rfp_table.py DELETED
@@ -1,31 +0,0 @@
1
- """update rfp table
2
-
3
- Revision ID: 7fae52a30825
4
- Revises: 0402a665d940
5
- Create Date: 2025-06-04 11:00:24.081115
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
-
14
- # revision identifiers, used by Alembic.
15
- revision: str = '7fae52a30825'
16
- down_revision: Union[str, None] = '0402a665d940'
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- """Upgrade schema."""
23
- status = sa.Enum("ACTIVE", "IN_PROGRESS", "NOT_YET_STARTED", "CUSTOMER_HOLD", "UNSOLVED_ISSUES", "AWARDED", "PROTESTED", name="rfpstatus")
24
- status.create(op.get_bind())
25
- op.add_column("rfps", sa.Column("status", status, nullable=False, server_default="ACTIVE"))
26
-
27
-
28
- def downgrade() -> None:
29
- """Downgrade schema."""
30
- sa.Enum(name='rfpstatus').drop(op.get_bind(), checkfirst=True)
31
- op.drop_column("rfps", "status")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/7fb86c16841c_add_flag_in_rfp_table.py DELETED
@@ -1,36 +0,0 @@
1
- """add flag in rfp table
2
-
3
- Revision ID: 7fb86c16841c
4
- Revises: b5d2f8b83d7e
5
- Create Date: 2025-06-06 11:06:57.084499
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
-
14
- # revision identifiers, used by Alembic.
15
- revision: str = '7fb86c16841c'
16
- down_revision: Union[str, None] = 'b5d2f8b83d7e'
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- """Upgrade schema."""
23
- op.add_column("rfps", sa.Column("flag", sa.Boolean(), nullable=True, server_default=sa.text("FALSE")))
24
- op.execute("UPDATE rfps SET flag = FALSE")
25
- op.alter_column(
26
- "rfps",
27
- "flag",
28
- existing_type=sa.Boolean(),
29
- nullable=False,
30
- server_default=sa.text("FALSE"),
31
- )
32
-
33
-
34
- def downgrade() -> None:
35
- """Downgrade schema."""
36
- op.drop_column("rfps", "flag")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/80b53babd4e6_update_flag_column_name.py DELETED
@@ -1,36 +0,0 @@
1
- """update flag column name
2
-
3
- Revision ID: 80b53babd4e6
4
- Revises: 7fb86c16841c
5
- Create Date: 2025-06-06 11:29:29.960724
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
-
14
- # revision identifiers, used by Alembic.
15
- revision: str = '80b53babd4e6'
16
- down_revision: Union[str, None] = '7fb86c16841c'
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- """Upgrade schema."""
23
- op.alter_column(
24
- 'rfps',
25
- 'flag',
26
- new_column_name='comparative_weights_locked'
27
- )
28
-
29
-
30
- def downgrade() -> None:
31
- """Downgrade schema."""
32
- op.alter_column(
33
- 'rfps',
34
- 'comparative_weights_locked',
35
- new_column_name='flag'
36
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/a3bbab758ddb_categorize_the_analysis.py DELETED
@@ -1,91 +0,0 @@
1
- """categorize the analysis
2
-
3
- Revision ID: a3bbab758ddb
4
- Revises: 7fae52a30825
5
- Create Date: 2025-06-04 14:01:24.366580
6
-
7
- """
8
-
9
- from typing import Sequence, Union
10
-
11
- from alembic import op
12
- import sqlalchemy as sa
13
-
14
-
15
- # revision identifiers, used by Alembic.
16
- revision: str = "a3bbab758ddb"
17
- down_revision: Union[str, None] = "7fae52a30825"
18
- branch_labels: Union[str, Sequence[str], None] = None
19
- depends_on: Union[str, Sequence[str], None] = None
20
-
21
-
22
- def upgrade() -> None:
23
- """Upgrade schema."""
24
- strength_category_enum = sa.Enum(
25
- "TECHNICAL", "MANAGEMENT", "PAST_PERFORMANCE", "PRICE", name="strengthcategory"
26
- )
27
- strength_category_enum.create(op.get_bind())
28
- op.add_column(
29
- "proposal_strengths",
30
- sa.Column(
31
- "category",
32
- strength_category_enum,
33
- nullable=False,
34
- server_default="TECHNICAL",
35
- ),
36
- )
37
- weakness_category_enum = sa.Enum(
38
- "TECHNICAL", "MANAGEMENT", "PAST_PERFORMANCE", "PRICE", name="weaknesscategory"
39
- )
40
- weakness_category_enum.create(op.get_bind())
41
- op.add_column(
42
- "proposal_weaknesses",
43
- sa.Column(
44
- "category",
45
- weakness_category_enum,
46
- nullable=False,
47
- server_default="TECHNICAL",
48
- ),
49
- )
50
- rejection_category_enum = sa.Enum(
51
- "TECHNICAL", "MANAGEMENT", "PAST_PERFORMANCE", "PRICE", name="rejectioncategory"
52
- )
53
- rejection_category_enum.create(op.get_bind())
54
- op.add_column(
55
- "proposal_rejection_reasons",
56
- sa.Column(
57
- "category",
58
- rejection_category_enum,
59
- nullable=False,
60
- server_default="TECHNICAL",
61
- ),
62
- )
63
- improvement_category_enum = sa.Enum(
64
- "TECHNICAL",
65
- "MANAGEMENT",
66
- "PAST_PERFORMANCE",
67
- "PRICE",
68
- name="improvementcategory",
69
- )
70
- improvement_category_enum.create(op.get_bind())
71
- op.add_column(
72
- "proposal_improvements",
73
- sa.Column(
74
- "category",
75
- improvement_category_enum,
76
- nullable=False,
77
- server_default="TECHNICAL",
78
- ),
79
- )
80
-
81
-
82
- def downgrade() -> None:
83
- """Downgrade schema."""
84
- sa.Enum(name="strengthcategory").drop(op.get_bind(), checkfirst=True)
85
- op.drop_column("proposal_strengths", "category")
86
- sa.Enum(name="weaknesscategory").drop(op.get_bind(), checkfirst=True)
87
- op.drop_column("proposal_weaknesses", "category")
88
- sa.Enum(name="rejectioncategory").drop(op.get_bind(), checkfirst=True)
89
- op.drop_column("proposal_rejection_reasons", "category")
90
- sa.Enum(name="improvementcategory").drop(op.get_bind(), checkfirst=True)
91
- op.drop_column("proposal_improvements", "category")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/b5d2f8b83d7e_add_flag_and_status.py DELETED
@@ -1,41 +0,0 @@
1
- """add flag and status
2
-
3
- Revision ID: b5d2f8b83d7e
4
- Revises: 3ba37b484f31
5
- Create Date: 2025-06-05 20:00:52.297117
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
-
14
- # revision identifiers, used by Alembic.
15
- revision: str = 'b5d2f8b83d7e'
16
- down_revision: Union[str, None] = '3ba37b484f31'
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- """Upgrade schema."""
23
- op.add_column("comparative_weights", sa.Column("flag", sa.Boolean(), nullable=True, server_default=sa.text("FALSE")))
24
- op.execute("UPDATE comparative_weights SET flag = FALSE")
25
- op.alter_column(
26
- "comparative_weights",
27
- "flag",
28
- existing_type=sa.Boolean(),
29
- nullable=False,
30
- server_default=sa.text("FALSE"),
31
- )
32
- analysis_enum = sa.Enum("ACCEPTED", "REJECTED", "PENDING", name="analysisstatus")
33
- analysis_enum.create(op.get_bind())
34
- op.add_column("analysis", sa.Column("status", analysis_enum, nullable=False, server_default="PENDING"))
35
-
36
- def downgrade() -> None:
37
- """Downgrade schema."""
38
- op.drop_column("comparative_weights", "flag")
39
-
40
- sa.Enum(name="analysisstatus").drop(op.get_bind(), checkfirst=True)
41
- op.drop_column("analysis", "status")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/c70330d677c0_add_evaluation_criteria.py DELETED
@@ -1,38 +0,0 @@
1
- """add evaluation_criteria
2
-
3
- Revision ID: c70330d677c0
4
- Revises: e825902b2e8a
5
- Create Date: 2025-06-02 19:38:53.073958
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
- from sqlalchemy.dialects import postgresql
14
-
15
-
16
- # revision identifiers, used by Alembic.
17
- revision: str = 'c70330d677c0'
18
- down_revision: Union[str, None] = 'e825902b2e8a'
19
- branch_labels: Union[str, Sequence[str], None] = None
20
- depends_on: Union[str, Sequence[str], None] = None
21
-
22
-
23
- def upgrade() -> None:
24
- """Upgrade schema."""
25
- op.create_table(
26
- "evaluation_criteria_details",
27
- sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
28
- sa.Column("rfp_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False),
29
- sa.Column("evaluation_criteria", sa.String(), nullable=True),
30
- sa.Column("evaluation_criteria_type", sa.Enum("EVALUATION", "GATE", name="evaluationcriteriatype"), nullable=False),
31
- sa.Column("created_at", sa.DateTime(), nullable=False),
32
- sa.Column("updated_at", sa.DateTime(), nullable=False),
33
- )
34
-
35
-
36
- def downgrade() -> None:
37
- """Downgrade schema."""
38
- op.drop_table("evaluation_criteria_details")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
alembic/versions/e825902b2e8a_alter_tables.py DELETED
@@ -1,49 +0,0 @@
1
- """alter tables
2
-
3
- Revision ID: e825902b2e8a
4
- Revises: 11d43047d470
5
- Create Date: 2025-06-02 16:32:45.552509
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
- from sqlalchemy.dialects import postgresql
14
-
15
- # revision identifiers, used by Alembic.
16
- revision: str = 'e825902b2e8a'
17
- down_revision: Union[str, None] = '11d43047d470'
18
- branch_labels: Union[str, Sequence[str], None] = None
19
- depends_on: Union[str, Sequence[str], None] = None
20
-
21
-
22
- def upgrade() -> None:
23
- """Upgrade schema."""
24
- op.add_column("rfps", sa.Column("name", sa.String(), nullable=True))
25
-
26
- op.add_column("proposals", sa.Column("tep", sa.String(), nullable=True))
27
-
28
- gate_criteria_enum = sa.Enum('PASS', 'FAIL', 'IN_REVIEW', name='gatecriteria')
29
- gate_criteria_enum.create(op.get_bind())
30
- op.add_column('proposals', sa.Column('gate_criteria', gate_criteria_enum, nullable=False, server_default='IN_REVIEW'))
31
-
32
- op.create_table(
33
- "letters",
34
- sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
35
- sa.Column("proposal_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("proposals.id", ondelete="CASCADE"), nullable=False),
36
- sa.Column("letter", sa.Text(), nullable=True),
37
- sa.Column("letter_type", sa.Enum("UO", "DEBRIEF", name="lettertype"), nullable=False),
38
- sa.Column("created_at", sa.DateTime(), nullable=False),
39
- sa.Column("updated_at", sa.DateTime(), nullable=False),
40
- )
41
-
42
-
43
- def downgrade() -> None:
44
- """Downgrade schema."""
45
- op.drop_table("letters")
46
- sa.Enum(name='gatecriteria').drop(op.get_bind(), checkfirst=True)
47
- op.drop_column("proposals", "gate_criteria")
48
- op.drop_column("proposals", "tep")
49
- op.drop_column("rfps", "name")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
poetry.lock DELETED
The diff for this file is too large to render. See raw diff
 
pyproject.toml DELETED
@@ -1,32 +0,0 @@
1
- [tool.poetry]
2
- name = "proposal-evaluation"
3
- version = "0.1.0"
4
- description = ""
5
- authors = ["Aryan Jain <77013565+aryanjain1908@users.noreply.github.com>"]
6
- readme = "README.md"
7
-
8
- [tool.poetry.dependencies]
9
- python = "^3.12"
10
- uvicorn = "^0.34.2"
11
- fastapi = "^0.115.12"
12
- python-dotenv = "^1.1.0"
13
- alembic = "^1.16.1"
14
- asyncpg = "^0.30.0"
15
- loguru = "^0.7.3"
16
- pydantic = "^2.11.5"
17
- aiosqlite = "^0.21.0"
18
- python-multipart = "^0.0.20"
19
- pypdf2 = "^3.0.1"
20
- pymupdf = "^1.26.0"
21
- python-docx = "^1.1.2"
22
- docx2pdf = "^0.1.8"
23
- openpyxl = "^3.1.5"
24
- pandas = "^2.3.0"
25
- numpy = "^2.2.6"
26
- aiofiles = "^24.1.0"
27
- markdown2 = "^2.5.3"
28
-
29
-
30
- [build-system]
31
- requires = ["poetry-core"]
32
- build-backend = "poetry.core.masonry.api"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/__init__.py DELETED
@@ -1,15 +0,0 @@
1
- from dotenv import load_dotenv
2
- import os
3
-
4
- from .app import app
5
- from .config import logger
6
-
7
- load_dotenv()
8
-
9
- if not os.getenv("SQLALCHEMY_DATABASE_URI"):
10
- logger.error("SQLALCHEMY_DATABASE_URI is not set")
11
- raise ValueError("SQLALCHEMY_DATABASE_URI is not set")
12
-
13
- __all__ = ["app"]
14
- __version__ = "0.1.0"
15
- __author__ = "Aryan Jain"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app.py DELETED
@@ -1,65 +0,0 @@
1
- import asyncio
2
- import os
3
- from contextlib import asynccontextmanager
4
- from threading import Thread
5
-
6
- from alembic import command
7
- from alembic.config import Config
8
- from fastapi import FastAPI
9
- from fastapi.middleware.cors import CORSMiddleware
10
-
11
- from src.config import DatabaseConfig, logger
12
- from src.controllers import api_router
13
-
14
-
15
-
16
- def run_upgrade(connection, alembic_config: Config):
17
- alembic_config.attributes["connection"] = connection
18
- command.upgrade(alembic_config, "head")
19
-
20
-
21
- async def run_migrations():
22
- logger.info("Running migrations if any...")
23
- alembic_config = Config("alembic.ini")
24
- alembic_config.set_main_option(
25
- "sqlalchemy.url", os.getenv("SQLALCHEMY_DATABASE_URI")
26
- )
27
- async with DatabaseConfig.get_engine().begin() as session:
28
- await session.run_sync(run_upgrade, alembic_config)
29
-
30
-
31
- @asynccontextmanager
32
- async def lifespan(app: FastAPI):
33
- try:
34
- logger.info("Starting up the application...")
35
- await run_migrations()
36
- logger.info("Application started successfully...")
37
- yield
38
- except Exception as e:
39
- logger.error(f"Error during startup: {str(e)}")
40
- raise
41
- finally:
42
- logger.info("Shutting down the application...")
43
- logger.info("Application shutdown complete.")
44
-
45
-
46
- app = FastAPI(lifespan=lifespan)
47
-
48
-
49
- app.add_middleware(
50
- CORSMiddleware,
51
- allow_origins=os.getenv(
52
- "CORS_ALLOW_ORIGINS", "http://localhost, http://127.0.0.1"
53
- ).split(", "),
54
- allow_credentials=True,
55
- allow_methods=["*"],
56
- allow_headers=["*"],
57
- )
58
-
59
-
60
- @app.get("/")
61
- async def check_health():
62
- return {"response": "Service is healthy!"}
63
-
64
-
65
- app.include_router(api_router, prefix="/api/v1")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/config/__init__.py DELETED
@@ -1,9 +0,0 @@
1
- from ._databse_config import DatabaseConfig
2
- from ._logger import logger
3
-
4
- __all__ = [
5
- "DatabaseConfig",
6
- "logger",
7
- ]
8
- __version__ = "0.1.0"
9
- __author__ = "Aryan Jain"
 
 
 
 
 
 
 
 
 
 
src/config/_databse_config.py DELETED
@@ -1,37 +0,0 @@
1
- import os
2
- import threading
3
- from contextlib import asynccontextmanager
4
-
5
- from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
6
-
7
-
8
- class DatabaseConfig:
9
- _thread_local = threading.local()
10
-
11
- @classmethod
12
- def get_engine(cls):
13
- if not hasattr(cls._thread_local, "engine"):
14
- cls._thread_local.engine = create_async_engine(
15
- url=os.getenv("SQLALCHEMY_DATABASE_URI"),
16
- pool_pre_ping=True,
17
- pool_recycle=3600,
18
- )
19
- return cls._thread_local.engine
20
-
21
- @classmethod
22
- def _get_session_factory(cls):
23
- if not hasattr(cls._thread_local, "session_factory"):
24
- cls._thread_local.session_factory = async_sessionmaker(
25
- bind=cls.get_engine(),
26
- autoflush=False,
27
- autocommit=False,
28
- expire_on_commit=False,
29
- )
30
- return cls._thread_local.session_factory
31
-
32
- @classmethod
33
- @asynccontextmanager
34
- async def async_session(cls):
35
- session_factory = cls._get_session_factory()
36
- async with session_factory() as session:
37
- yield session
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/config/_logger.py DELETED
@@ -1,8 +0,0 @@
1
- import os
2
-
3
- from loguru import logger
4
-
5
- LOG_FILE = os.getenv("LOG_FILE")
6
- if LOG_FILE:
7
- LOG_RETENTION = os.getenv("LOG_RETENTION", "90 days")
8
- logger.add(LOG_FILE, retention=LOG_RETENTION)
 
 
 
 
 
 
 
 
 
src/controllers/__init__.py DELETED
@@ -1,28 +0,0 @@
1
- from fastapi import APIRouter
2
- from ._rfp_controller import RFPController
3
- from ._proposal_controller import ProposalController
4
- from ._proposal_ai_analysis_controller import ProposalAIController
5
- from ._proposal_detailed_analysis_controller import ProposalDetailedController
6
- from ._letter_controller import LetterController
7
- from ._evaluation_controller import EvaluationController
8
- from ._comparative_weight_controller import ComparativeWeightController
9
- from ._proposal_evaluation_controller import ProposalEvaluationController
10
- from ._analysis_conteoller import AnalysisController
11
- from ._file_controller import FileController
12
-
13
- api_router = APIRouter()
14
-
15
- api_router.include_router(RFPController().router)
16
- api_router.include_router(ProposalController().router)
17
- # api_router.include_router(ProposalAIController().router)
18
- # api_router.include_router(ProposalDetailedController().router)
19
- api_router.include_router(LetterController().router)
20
- api_router.include_router(EvaluationController().router)
21
- api_router.include_router(ComparativeWeightController().router)
22
- api_router.include_router(ProposalEvaluationController().router)
23
- api_router.include_router(AnalysisController().router)
24
- api_router.include_router(FileController().router)
25
-
26
- __all__ = ["api_router"]
27
- __version__ = "0.1.0"
28
- __author__ = "Aryan Jain"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_analysis_conteoller.py DELETED
@@ -1,249 +0,0 @@
1
- from fastapi import APIRouter, Body, HTTPException, Query, Path
2
- from pydantic import BaseModel
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
- from src.models import AnalysisType, AnalysisStatus
9
- from src.services import AnalysisService
10
-
11
-
12
- class Analysis(BaseModel):
13
- id: UUID
14
- evaluation_id: UUID
15
- insights: str
16
- analysis_type: AnalysisType
17
- status: AnalysisStatus
18
- created_at: datetime
19
- updated_at: datetime
20
-
21
-
22
- class Response(BaseModel):
23
- status: str
24
- data: Optional[List[Analysis]] = None
25
-
26
-
27
- class CreateAnalysis(BaseModel):
28
- insights: str
29
- analysis_type: AnalysisType
30
- status: Optional[AnalysisStatus] = None
31
-
32
-
33
- class CreateAnalysisRequest(BaseModel):
34
- data: List[CreateAnalysis]
35
- analysis_score_type: Optional[str] = None
36
-
37
-
38
- class UpdateAnalysis(BaseModel):
39
- insights: Optional[str] = None
40
- analysis_type: Optional[AnalysisType] = None
41
- status: Optional[AnalysisStatus] = None
42
-
43
-
44
- class Deleteresponse(BaseModel):
45
- status: str
46
-
47
-
48
- class AnalysisController:
49
- def __init__(self):
50
- self.service = AnalysisService
51
- self.router = APIRouter()
52
- self.router.add_api_route(
53
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
54
- self.get_analysis_by_proposal_and_rfp_id_and_evaluation_id,
55
- methods=["GET"],
56
- response_model=Response,
57
- tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
58
- )
59
- self.router.add_api_route(
60
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}",
61
- self.get_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id,
62
- methods=["GET"],
63
- response_model=Response,
64
- tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"],
65
- )
66
-
67
- self.router.add_api_route(
68
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
69
- self.create_analysis_by_proposal_and_rfp_id_and_evaluation_id,
70
- methods=["POST"],
71
- response_model=Response,
72
- tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
73
- )
74
-
75
- self.router.add_api_route(
76
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
77
- self.update_analysis_by_proposal_and_rfp_id_and_evaluation_id,
78
- methods=["PUT"],
79
- response_model=Response,
80
- tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
81
- )
82
- self.router.add_api_route(
83
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}",
84
- self.update_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id,
85
- methods=["PUT"],
86
- response_model=Response,
87
- tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"],
88
- )
89
-
90
- self.router.add_api_route(
91
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
92
- self.delete_analysis_by_proposal_and_rfp_id_and_evaluation_id,
93
- methods=["DELETE"],
94
- response_model=Deleteresponse,
95
- tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
96
- )
97
- self.router.add_api_route(
98
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}",
99
- self.delete_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id,
100
- methods=["DELETE"],
101
- response_model=Deleteresponse,
102
- tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"],
103
- )
104
-
105
- async def get_analysis_by_proposal_and_rfp_id_and_evaluation_id(
106
- self,
107
- rfp_id: str = Path(...),
108
- proposal_id: str = Path(...),
109
- evaluation_id: str = Path(...),
110
- analysis_type: AnalysisType = Query(None),
111
- ):
112
- try:
113
- async with self.service() as service:
114
- results = await service.get_analysis(
115
- evaluation_id=evaluation_id, analysis_type=analysis_type
116
- )
117
- return Response(
118
- status="success",
119
- data=[Analysis(**result) for result in results],
120
- )
121
- except Exception as e:
122
- logger.error(e)
123
- raise HTTPException(status_code=500, detail=str(e))
124
-
125
- async def get_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id(
126
- self,
127
- rfp_id: str = Path(...),
128
- proposal_id: str = Path(...),
129
- evaluation_id: str = Path(...),
130
- id: str = Path(...),
131
- ):
132
- try:
133
- async with self.service() as service:
134
- results = await service.get_analysis(evaluation_id=evaluation_id, id=id)
135
- return Response(
136
- status="success",
137
- data=[Analysis(**result) for result in results],
138
- )
139
- except Exception as e:
140
- logger.error(e)
141
- raise HTTPException(status_code=500, detail=str(e))
142
-
143
- async def create_analysis_by_proposal_and_rfp_id_and_evaluation_id(
144
- self,
145
- rfp_id: str = Path(...),
146
- proposal_id: str = Path(...),
147
- evaluation_id: str = Path(...),
148
- analysis: CreateAnalysisRequest = Body(...),
149
- ):
150
- try:
151
- async with self.service() as service:
152
- evaluation_analysis = analysis.model_dump(
153
- mode="json", exclude_unset=True
154
- )
155
- results = await service.create_analysis(
156
- evaluation_id=evaluation_id,
157
- evaluation_analysis=evaluation_analysis["data"],
158
- analysis_score_type=(
159
- evaluation_analysis["analysis_score_type"]
160
- if "analysis_score_type" in evaluation_analysis
161
- else None
162
- ),
163
- )
164
- return Response(
165
- status="success",
166
- data=[Analysis(**result) for result in results],
167
- )
168
- except Exception as e:
169
- logger.error(e)
170
- raise HTTPException(status_code=500, detail=str(e))
171
-
172
- async def update_analysis_by_proposal_and_rfp_id_and_evaluation_id(
173
- self,
174
- rfp_id: str = Path(...),
175
- proposal_id: str = Path(...),
176
- evaluation_id: str = Path(...),
177
- analysis: CreateAnalysisRequest = Body(...),
178
- ):
179
- try:
180
- async with self.service() as service:
181
- results = await service.update_analysis(
182
- evaluation_id=evaluation_id,
183
- proposal_analysis=analysis.model_dump(
184
- mode="json", exclude_unset=True
185
- )["data"],
186
- )
187
- return Response(
188
- status="success",
189
- data=[Analysis(**result) for result in results],
190
- )
191
- except Exception as e:
192
- logger.error(e)
193
- raise HTTPException(status_code=500, detail=str(e))
194
-
195
- async def update_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id(
196
- self,
197
- rfp_id: str = Path(...),
198
- proposal_id: str = Path(...),
199
- evaluation_id: str = Path(...),
200
- id: str = Path(...),
201
- analysis: UpdateAnalysis = Body(...),
202
- ):
203
- try:
204
- async with self.service() as service:
205
- results = await service.update_analysis(
206
- evaluation_id=evaluation_id,
207
- id=id,
208
- proposal_analysis=analysis.model_dump(
209
- mode="json", exclude_unset=True
210
- ),
211
- )
212
- return Response(
213
- status="success",
214
- data=[Analysis(**result) for result in results],
215
- )
216
- except Exception as e:
217
- logger.error(e)
218
- raise HTTPException(status_code=500, detail=str(e))
219
-
220
- async def delete_analysis_by_proposal_and_rfp_id_and_evaluation_id(
221
- self,
222
- rfp_id: str = Path(...),
223
- proposal_id: str = Path(...),
224
- evaluation_id: str = Path(...),
225
- ):
226
- try:
227
- async with self.service() as service:
228
- results = await service.delete_analysis(evaluation_id=evaluation_id)
229
- return Deleteresponse(status="success")
230
- except Exception as e:
231
- logger.error(e)
232
- raise HTTPException(status_code=500, detail=str(e))
233
-
234
- async def delete_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id(
235
- self,
236
- rfp_id: str = Path(...),
237
- proposal_id: str = Path(...),
238
- evaluation_id: str = Path(...),
239
- id: str = Path(...),
240
- ):
241
- try:
242
- async with self.service() as service:
243
- results = await service.delete_analysis(
244
- evaluation_id=evaluation_id, id=id
245
- )
246
- return Deleteresponse(status="success")
247
- except Exception as e:
248
- logger.error(e)
249
- raise HTTPException(status_code=500, detail=str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_comparative_weight_controller.py DELETED
@@ -1,237 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path
2
- from pydantic import BaseModel
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
- from src.models import Weights
9
- from src.services import ComparativeWeightService
10
-
11
-
12
- class ComparitiveWeight(BaseModel):
13
- id: UUID
14
- rfp_id: UUID
15
- technical_weight: Weights
16
- management_weight: Weights
17
- past_performance_weight: Weights
18
- price_weight: Weights
19
- strengths_weight: float
20
- weaknesses_weight: float
21
- flag: Optional[bool] = False
22
- created_at: datetime
23
- updated_at: datetime
24
-
25
-
26
- class ResponseComparativeWeight(BaseModel):
27
- status: str
28
- data: Optional[List[ComparitiveWeight]]
29
-
30
-
31
- class DeleteResponse(BaseModel):
32
- status: str
33
-
34
-
35
- class ComparativeWeightRequest(BaseModel):
36
- technical_weight: Optional[Weights] = None
37
- management_weight: Optional[Weights] = None
38
- past_performance_weight: Optional[Weights] = None
39
- price_weight: Optional[Weights] = None
40
- strengths_weight: Optional[float] = None
41
- weaknesses_weight: Optional[float] = None
42
- flag: Optional[bool] = None
43
-
44
-
45
- class ComparativeWeightController:
46
- def __init__(self):
47
- self.comparative_weight_service = ComparativeWeightService
48
- self.router = APIRouter()
49
- self.router.add_api_route(
50
- "/rfps/{rfp_id}/comparative-weights",
51
- self.get_comparative_weights_by_rfp_id,
52
- methods=["GET"],
53
- response_model=ResponseComparativeWeight,
54
- tags=["Comparative Weights by RFP ID"],
55
- )
56
- self.router.add_api_route(
57
- "/rfps/{rfp_id}/comparative-weights/{id}",
58
- self.get_comparative_weights_by_id,
59
- methods=["GET"],
60
- response_model=ResponseComparativeWeight,
61
- tags=["Comparative Weights by RFP ID and ID"],
62
- )
63
- self.router.add_api_route(
64
- "/rfps/{rfp_id}/comparative-weights",
65
- self.create_comparative_weights,
66
- methods=["POST"],
67
- response_model=ResponseComparativeWeight,
68
- tags=["Comparative Weights by RFP ID"],
69
- )
70
- self.router.add_api_route(
71
- "/rfps/{rfp_id}/comparative-weights/{id}",
72
- self.update_comparative_weights_by_id,
73
- methods=["PUT"],
74
- response_model=ResponseComparativeWeight,
75
- tags=["Comparative Weights by RFP ID and ID"],
76
- )
77
- self.router.add_api_route(
78
- "/rfps/{rfp_id}/comparative-weights",
79
- self.update_comparative_weights,
80
- methods=["PUT"],
81
- response_model=ResponseComparativeWeight,
82
- tags=["Comparative Weights by RFP ID"],
83
- )
84
- self.router.add_api_route(
85
- "/rfps/{rfp_id}/comparative-weights",
86
- self.delete_comparative_weights,
87
- methods=["DELETE"],
88
- response_model=DeleteResponse,
89
- tags=["Comparative Weights by RFP ID"],
90
- )
91
- self.router.add_api_route(
92
- "/rfps/{rfp_id}/comparative-weights/{id}",
93
- self.delete_comparative_weights_by_id,
94
- methods=["DELETE"],
95
- response_model=DeleteResponse,
96
- tags=["Comparative Weights by RFP ID and ID"],
97
- )
98
-
99
- async def get_comparative_weights_by_rfp_id(self, rfp_id: str = Path(...)):
100
- try:
101
- async with self.comparative_weight_service() as service:
102
- comparative_weights = await service.get_comparative_weights(
103
- rfp_id=rfp_id
104
- )
105
- return ResponseComparativeWeight(
106
- status="success",
107
- data=[
108
- ComparitiveWeight(**comparative_weight)
109
- for comparative_weight in comparative_weights
110
- ],
111
- )
112
- except Exception as e:
113
- logger.error(f"Error getting comparative weights by rfp id: {e}")
114
- raise HTTPException(
115
- status_code=500, detail="Error getting comparative weights by rfp id"
116
- )
117
-
118
- async def get_comparative_weights_by_id(
119
- self, rfp_id: str = Path(...), id: str = Path(...)
120
- ):
121
- try:
122
- async with self.comparative_weight_service() as service:
123
- comparative_weights = await service.get_comparative_weights(
124
- rfp_id=rfp_id, id=id
125
- )
126
- return ResponseComparativeWeight(
127
- status="success",
128
- data=[
129
- ComparitiveWeight(**comparative_weight)
130
- for comparative_weight in comparative_weights
131
- ],
132
- )
133
- except Exception as e:
134
- logger.error(f"Error getting comparative weights by id: {e}")
135
- raise HTTPException(
136
- status_code=500, detail="Error getting comparative weights by id"
137
- )
138
-
139
- async def create_comparative_weights(
140
- self, comparative_weights: ComparativeWeightRequest, rfp_id: str = Path(...)
141
- ):
142
- try:
143
- async with self.comparative_weight_service() as service:
144
- comparative_weights = await service.create_comparative_weights(
145
- rfp_id=rfp_id,
146
- comparative_weights=comparative_weights.model_dump(
147
- mode="json", exclude_unset=True
148
- ),
149
- )
150
- return ResponseComparativeWeight(
151
- status="success",
152
- data=[
153
- ComparitiveWeight(**comparative_weight)
154
- for comparative_weight in comparative_weights
155
- ],
156
- )
157
- except Exception as e:
158
- logger.error(f"Error creating comparative weights: {e}")
159
- raise HTTPException(
160
- status_code=500, detail="Error creating comparative weights"
161
- )
162
-
163
- async def update_comparative_weights(
164
- self, comparative_weights: ComparativeWeightRequest, rfp_id: str = Path(...)
165
- ):
166
- try:
167
- async with self.comparative_weight_service() as service:
168
- comparative_weights = await service.update_comparative_weights(
169
- rfp_id=rfp_id,
170
- comparative_weights=comparative_weights.model_dump(
171
- mode="json", exclude_unset=True
172
- ),
173
- )
174
- return ResponseComparativeWeight(
175
- status="success",
176
- data=[
177
- ComparitiveWeight(**comparative_weight)
178
- for comparative_weight in comparative_weights
179
- ],
180
- )
181
- except Exception as e:
182
- logger.error(f"Error updating comparative weights: {e}")
183
- raise HTTPException(
184
- status_code=500, detail="Error updating comparative weights"
185
- )
186
-
187
- async def update_comparative_weights_by_id(
188
- self,
189
- comparative_weights: ComparativeWeightRequest,
190
- rfp_id: str = Path(...),
191
- id: str = Path(...),
192
- ):
193
- try:
194
- async with self.comparative_weight_service() as service:
195
- comparative_weights = await service.update_comparative_weights(
196
- rfp_id=rfp_id,
197
- id=id,
198
- comparative_weights=comparative_weights.model_dump(
199
- mode="json", exclude_unset=True
200
- ),
201
- )
202
- return ResponseComparativeWeight(
203
- status="success",
204
- data=[
205
- ComparitiveWeight(**comparative_weight)
206
- for comparative_weight in comparative_weights
207
- ],
208
- )
209
- except Exception as e:
210
- logger.error(f"Error updating comparative weights by id: {e}")
211
- raise HTTPException(
212
- status_code=500, detail="Error updating comparative weights by id"
213
- )
214
-
215
- async def delete_comparative_weights(self, rfp_id: str = Path(...)):
216
- try:
217
- async with self.comparative_weight_service() as service:
218
- await service.delete_comparative_weights(rfp_id=rfp_id)
219
- return DeleteResponse(status="success")
220
- except Exception as e:
221
- logger.error(f"Error deleting comparative weights: {e}")
222
- raise HTTPException(
223
- status_code=500, detail="Error deleting comparative weights"
224
- )
225
-
226
- async def delete_comparative_weights_by_id(
227
- self, rfp_id: str = Path(...), id: str = Path(...)
228
- ):
229
- try:
230
- async with self.comparative_weight_service() as service:
231
- await service.delete_comparative_weights(rfp_id=rfp_id, id=id)
232
- return DeleteResponse(status="success")
233
- except Exception as e:
234
- logger.error(f"Error deleting comparative weights: {e}")
235
- raise HTTPException(
236
- status_code=500, detail="Error deleting comparative weights"
237
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_evaluation_controller.py DELETED
@@ -1,299 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path
2
- from pydantic import BaseModel
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
- from src.services import EvaluationService
9
- from src.models import EvaluationCriteriaType
10
-
11
-
12
- class EvaluationCriteria(BaseModel):
13
- id: UUID
14
- rfp_id: UUID
15
- evaluation_criteria: str
16
- evaluation_criteria_type: EvaluationCriteriaType
17
- created_at: datetime
18
- updated_at: datetime
19
-
20
-
21
- class EvaluationResponse(BaseModel):
22
- status: str
23
- data: List[EvaluationCriteria]
24
-
25
-
26
- class EvaluationRequest(BaseModel):
27
- rfp_id: UUID
28
- evaluation_criteria: str
29
- evaluation_criteria_type: EvaluationCriteriaType
30
-
31
-
32
- class CreateEvaluationRequest(BaseModel):
33
- evaluation_criteria: str
34
- evaluation_criteria_type: EvaluationCriteriaType
35
-
36
-
37
- class UpdateEvaluationRequest(BaseModel):
38
- rfp_id: Optional[UUID] = None
39
- evaluation_criteria: Optional[str] = None
40
- evaluation_criteria_type: Optional[EvaluationCriteriaType] = None
41
-
42
-
43
- class UpdateEvaluationCriteriaByRFPRequest(BaseModel):
44
- evaluation_criteria: Optional[str] = None
45
- evaluation_criteria_type: Optional[EvaluationCriteriaType] = None
46
-
47
-
48
- class DeleteResponse(BaseModel):
49
- status: str
50
-
51
-
52
- class EvaluationController:
53
- def __init__(self):
54
- self.evaluation_service = EvaluationService
55
- self.router = APIRouter()
56
- # self.router.add_api_route(
57
- # "/evaluation-criteria",
58
- # self.get_criteria,
59
- # methods=["GET"],
60
- # response_model=EvaluationResponse,
61
- # tags=["Evaluation Criteria"],
62
- # )
63
- # self.router.add_api_route(
64
- # "/evaluation-criteria/{id}",
65
- # self.get_criteria_by_id,
66
- # methods=["GET"],
67
- # response_model=EvaluationResponse,
68
- # tags=["Evaluation Criteria by ID"],
69
- # )
70
- self.router.add_api_route(
71
- "/rfps/{rfp_id}/evaluation-criteria",
72
- self.get_criteria_by_rfp_id,
73
- methods=["GET"],
74
- response_model=EvaluationResponse,
75
- tags=["Evaluation Criteria by RFP ID"],
76
- )
77
- self.router.add_api_route(
78
- "/rfps/{rfp_id}/evaluation-criteria/{id}",
79
- self.get_criteria_by_proposal_and_rfp_id,
80
- methods=["GET"],
81
- response_model=EvaluationResponse,
82
- tags=["Evaluation Criteria by Proposal and RFP ID"],
83
- )
84
- # self.router.add_api_route(
85
- # "/evaluation-criteria",
86
- # self.create_criteria,
87
- # methods=["POST"],
88
- # response_model=EvaluationResponse,
89
- # tags=["Evaluation Criteria"],
90
- # )
91
- self.router.add_api_route(
92
- "/rfps/{rfp_id}/evaluation-criteria",
93
- self.create_criteria_by_rfp_id,
94
- methods=["POST"],
95
- response_model=EvaluationResponse,
96
- tags=["Evaluation Criteria by RFP ID"],
97
- )
98
- # self.router.add_api_route(
99
- # "/evaluation-criteria/{id}",
100
- # self.update_criteria,
101
- # methods=["PUT"],
102
- # response_model=EvaluationResponse,
103
- # tags=["Evaluation Criteria by ID"],
104
- # )
105
- self.router.add_api_route(
106
- "/rfps/{rfp_id}/evaluation-criteria/{id}",
107
- self.update_criteria_by_proposal_and_rfp_id,
108
- methods=["PUT"],
109
- response_model=EvaluationResponse,
110
- tags=["Evaluation Criteria by Proposal and RFP ID"],
111
- )
112
- # self.router.add_api_route(
113
- # "/evaluation-criteria/{id}",
114
- # self.delete_criteria,
115
- # methods=["DELETE"],
116
- # response_model=DeleteResponse,
117
- # tags=["Evaluation Criteria by ID"],
118
- # )
119
- self.router.add_api_route(
120
- "/rfps/{rfp_id}/evaluation-criteria",
121
- self.delete_criteria_by_rfp_id,
122
- methods=["DELETE"],
123
- response_model=DeleteResponse,
124
- tags=["Evaluation Criteria by RFP ID"],
125
- )
126
- self.router.add_api_route(
127
- "/rfps/{rfp_id}/evaluation-criteria/{id}",
128
- self.delete_criteria_by_proposal_and_rfp_id,
129
- methods=["DELETE"],
130
- response_model=DeleteResponse,
131
- tags=["Evaluation Criteria by Proposal and RFP ID"],
132
- )
133
-
134
- async def get_criteria(self):
135
- try:
136
- async with self.evaluation_service() as service:
137
- result = await service.get_criteria()
138
- return EvaluationResponse(
139
- status="success", data=[EvaluationCriteria(**r) for r in result]
140
- )
141
- except Exception as e:
142
- logger.error(e)
143
- raise HTTPException(
144
- status_code=500, detail="Error fetching evaluation criteria"
145
- )
146
-
147
- async def get_criteria_by_id(self, id: str = Path(...)):
148
- try:
149
- async with self.evaluation_service() as service:
150
- result = await service.get_criteria(id=id)
151
- return EvaluationResponse(
152
- status="success", data=[EvaluationCriteria(**r) for r in result]
153
- )
154
- except Exception as e:
155
- logger.error(e)
156
- raise HTTPException(
157
- status_code=500, detail="Error fetching evaluation criteria"
158
- )
159
-
160
- async def get_criteria_by_rfp_id(
161
- self,
162
- rfp_id: str = Path(...),
163
- criteria_type: Optional[EvaluationCriteriaType] = Query(None),
164
- ):
165
- try:
166
- async with self.evaluation_service() as service:
167
- result = await service.get_criteria(
168
- rfp_id=rfp_id, evaluation_criteria_type=criteria_type
169
- )
170
- return EvaluationResponse(
171
- status="success", data=[EvaluationCriteria(**r) for r in result]
172
- )
173
- except Exception as e:
174
- logger.error(e)
175
- raise HTTPException(
176
- status_code=500, detail="Error fetching evaluation criteria"
177
- )
178
-
179
- async def get_criteria_by_proposal_and_rfp_id(
180
- self, rfp_id: str = Path(...), id: str = Path(...)
181
- ):
182
- try:
183
- async with self.evaluation_service() as service:
184
- result = await service.get_criteria(rfp_id=rfp_id, id=id)
185
- return EvaluationResponse(
186
- status="success", data=[EvaluationCriteria(**r) for r in result]
187
- )
188
- except Exception as e:
189
- logger.error(e)
190
- raise HTTPException(
191
- status_code=500, detail="Error fetching evaluation criteria"
192
- )
193
-
194
- async def create_criteria(self, criteria: EvaluationRequest):
195
- try:
196
- async with self.evaluation_service() as service:
197
- result = await service.create_criteria(
198
- criteria.model_dump(exclude_unset=True, mode="json")
199
- )
200
- return EvaluationResponse(
201
- status="success", data=[EvaluationCriteria(**r) for r in result]
202
- )
203
- except Exception as e:
204
- logger.error(e)
205
- raise HTTPException(
206
- status_code=500, detail="Error creating evaluation criteria"
207
- )
208
-
209
- async def create_criteria_by_rfp_id(
210
- self, criteria: CreateEvaluationRequest, rfp_id: str = Path(...)
211
- ):
212
- try:
213
- async with self.evaluation_service() as service:
214
- data = criteria.model_dump(exclude_unset=True, mode="json")
215
- data["rfp_id"] = rfp_id
216
- result = await service.create_criteria(evaluation_criteria=data)
217
- return EvaluationResponse(
218
- status="success", data=[EvaluationCriteria(**r) for r in result]
219
- )
220
- except Exception as e:
221
- logger.error(e)
222
- raise HTTPException(
223
- status_code=500, detail="Error creating evaluation criteria"
224
- )
225
-
226
- async def update_criteria(
227
- self, criteria: UpdateEvaluationRequest, id: str = Path(...)
228
- ):
229
- try:
230
- async with self.evaluation_service() as service:
231
- result = await service.update_criteria(
232
- id=id,
233
- evaluation_criteria=criteria.model_dump(
234
- exclude_unset=True, mode="json"
235
- ),
236
- )
237
- return EvaluationResponse(
238
- status="success", data=[EvaluationCriteria(**r) for r in result]
239
- )
240
- except Exception as e:
241
- logger.error(e)
242
- raise HTTPException(
243
- status_code=500, detail="Error updating evaluation criteria"
244
- )
245
-
246
- async def update_criteria_by_proposal_and_rfp_id(
247
- self,
248
- criteria: UpdateEvaluationCriteriaByRFPRequest,
249
- rfp_id: str = Path(...),
250
- id: str = Path(...),
251
- ):
252
- try:
253
- async with self.evaluation_service() as service:
254
- data = criteria.model_dump(exclude_unset=True, mode="json")
255
- data["rfp_id"] = rfp_id
256
- result = await service.update_criteria(id=id, evaluation_criteria=data)
257
- return EvaluationResponse(
258
- status="success", data=[EvaluationCriteria(**r) for r in result]
259
- )
260
- except Exception as e:
261
- logger.error(e)
262
- raise HTTPException(
263
- status_code=500, detail="Error updating evaluation criteria"
264
- )
265
-
266
- async def delete_criteria(self, id: str = Path(...)):
267
- try:
268
- async with self.evaluation_service() as service:
269
- await service.delete_criteria(id=id)
270
- return DeleteResponse(status="success")
271
- except Exception as e:
272
- logger.error(e)
273
- raise HTTPException(
274
- status_code=500, detail="Error deleting evaluation criteria"
275
- )
276
-
277
- async def delete_criteria_by_rfp_id(self, rfp_id: str = Path(...)):
278
- try:
279
- async with self.evaluation_service() as service:
280
- await service.delete_criteria(rfp_id=rfp_id)
281
- return DeleteResponse(status="success")
282
- except Exception as e:
283
- logger.error(e)
284
- raise HTTPException(
285
- status_code=500, detail="Error deleting evaluation criteria"
286
- )
287
-
288
- async def delete_criteria_by_proposal_and_rfp_id(
289
- self, rfp_id: str = Path(...), id: str = Path(...)
290
- ):
291
- try:
292
- async with self.evaluation_service() as service:
293
- await service.delete_criteria(rfp_id=rfp_id, id=id)
294
- return DeleteResponse(status="success")
295
- except Exception as e:
296
- logger.error(e)
297
- raise HTTPException(
298
- status_code=500, detail="Error deleting evaluation criteria"
299
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_file_controller.py DELETED
@@ -1,34 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path, UploadFile, File
2
- from pydantic import BaseModel
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
- from src.services import FileService
9
-
10
- import io
11
-
12
- class Response(BaseModel):
13
- status: str
14
- data: str
15
-
16
- class FileController:
17
- def __init__(self):
18
- self.service = FileService
19
- self.router = APIRouter()
20
- self.router.add_api_route(
21
- "/files",
22
- self.get_files_extrcated_text,
23
- methods=["POST"],
24
- tags=["Files"],
25
- )
26
-
27
- async def get_files_extrcated_text(self, file: UploadFile = File(...)):
28
- try:
29
- async with self.service() as service:
30
- result = await service.extract_file(file=file)
31
- return result
32
- except Exception as e:
33
- logger.error(e)
34
- raise HTTPException(status_code=500, detail="Error processing file")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_letter_controller.py DELETED
@@ -1,263 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path
2
- from pydantic import BaseModel
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
- from src.services import LetterService
9
- from src.models import LetterType
10
-
11
-
12
- class Letter(BaseModel):
13
- id: UUID
14
- proposal_id: UUID
15
- letter: str
16
- letter_type: LetterType
17
- created_at: datetime
18
- updated_at: datetime
19
-
20
-
21
- class Response(BaseModel):
22
- status: str
23
- data: List[Letter]
24
-
25
-
26
- class LetterRequest(BaseModel):
27
- proposal_id: UUID
28
- letter: str
29
- letter_type: LetterType
30
-
31
-
32
- class CreateLetterRequest(BaseModel):
33
- letter: str
34
- letter_type: LetterType
35
-
36
-
37
- class UpdateLetterRequest(BaseModel):
38
- proposal_id: Optional[UUID] = None
39
- letter: Optional[str] = None
40
- letter_type: Optional[LetterType] = None
41
-
42
-
43
- class UpdateLetterRequestByProposalAndId(BaseModel):
44
- letter: Optional[str] = None
45
- letter_type: Optional[LetterType] = None
46
-
47
-
48
- class DeleteResponse(BaseModel):
49
- status: str
50
-
51
-
52
- class LetterController:
53
- def __init__(self):
54
- self.letter_service = LetterService
55
- self.router = APIRouter()
56
- # self.router.add_api_route(
57
- # "/letters",
58
- # self.get_letters,
59
- # methods=["GET"],
60
- # response_model=Response,
61
- # tags=["Letters"],
62
- # )
63
- # self.router.add_api_route(
64
- # "/letters/{id}",
65
- # self.get_letters_by_id,
66
- # methods=["GET"],
67
- # response_model=Response,
68
- # tags=["Letters by ID"],
69
- # )
70
- self.router.add_api_route(
71
- "/rfps/{rfp_id}/proposals/{proposal_id}/letters",
72
- self.get_letters_by_proposal_id,
73
- methods=["GET"],
74
- response_model=Response,
75
- tags=["Letters by Proposal ID"],
76
- )
77
- self.router.add_api_route(
78
- "/rfps/{rfp_id}/proposals/{proposal_id}/letters/{id}",
79
- self.get_letters_by_proposal_and_id,
80
- methods=["GET"],
81
- response_model=Response,
82
- tags=["Letters by Proposal and ID"],
83
- )
84
- # self.router.add_api_route(
85
- # "/letters",
86
- # self.create_letter,
87
- # methods=["POST"],
88
- # response_model=Response,
89
- # tags=["Letters"],
90
- # )
91
- self.router.add_api_route(
92
- "/rfps/{rfp_id}/proposals/{proposal_id}/letters",
93
- self.create_letters_by_proposal_id,
94
- methods=["POST"],
95
- response_model=Response,
96
- tags=["Letters by Proposal ID"],
97
- )
98
- # self.router.add_api_route(
99
- # "/letters/{id}",
100
- # self.update_letter,
101
- # methods=["PUT"],
102
- # response_model=Response,
103
- # tags=["Letters by ID"],
104
- # )
105
- self.router.add_api_route(
106
- "/rfps/{rfp_id}/proposals/{proposal_id}/letters/{id}",
107
- self.update_letters_by_proposal_and_id,
108
- methods=["PUT"],
109
- response_model=Response,
110
- tags=["Letters by Proposal and ID"],
111
- )
112
- # self.router.add_api_route(
113
- # "/letters/{id}",
114
- # self.delete_letter,
115
- # methods=["DELETE"],
116
- # response_model=DeleteResponse,
117
- # tags=["Letters by ID"],
118
- # )
119
- self.router.add_api_route(
120
- "/rfps/{rfp_id}/proposals/{proposal_id}/letters",
121
- self.delete_letters_by_proposal_id,
122
- methods=["DELETE"],
123
- response_model=DeleteResponse,
124
- tags=["Letters by Proposal ID"],
125
- )
126
- self.router.add_api_route(
127
- "/rfps/{rfp_id}/proposals/{proposal_id}/letters/{id}",
128
- self.delete_letters_by_proposal_and_id,
129
- methods=["DELETE"],
130
- response_model=DeleteResponse,
131
- tags=["Letters by Proposal and ID"],
132
- )
133
-
134
- async def get_letters(self):
135
- try:
136
- async with self.letter_service() as service:
137
- result = await service.get_letters()
138
- return Response(status="success", data=[Letter(**r) for r in result])
139
- except Exception as e:
140
- logger.error(e)
141
- raise HTTPException(status_code=500, detail="Error fetching letters")
142
-
143
- async def get_letters_by_id(self, id: str = Path(...)):
144
- try:
145
- async with self.letter_service() as service:
146
- result = await service.get_letters(id=id)
147
- return Response(status="success", data=[Letter(**r) for r in result])
148
- except Exception as e:
149
- logger.error(e)
150
- raise HTTPException(status_code=500, detail="Error fetching letter")
151
-
152
- async def get_letters_by_proposal_id(
153
- self,
154
- proposal_id: str = Path(...),
155
- letter_type: LetterType = Query(None),
156
- rfp_id: str = Path(...),
157
- ):
158
- try:
159
- async with self.letter_service() as service:
160
- result = await service.get_letters(
161
- proposal_id=proposal_id, letter_type=letter_type
162
- )
163
- return Response(status="success", data=[Letter(**r) for r in result])
164
- except Exception as e:
165
- logger.error(e)
166
- raise HTTPException(status_code=500, detail="Error fetching letter")
167
-
168
- async def get_letters_by_proposal_and_id(
169
- self, proposal_id: str = Path(...), id: str = Path(...), rfp_id: str = Path(...)
170
- ):
171
- try:
172
- async with self.letter_service() as service:
173
- result = await service.get_letters(proposal_id=proposal_id, id=id)
174
- return Response(status="success", data=[Letter(**r) for r in result])
175
- except Exception as e:
176
- logger.error(e)
177
- raise HTTPException(status_code=500, detail="Error fetching letter")
178
-
179
- async def create_letter(self, letter: LetterRequest):
180
- try:
181
- async with self.letter_service() as service:
182
- result = await service.create_letter(
183
- letter.model_dump(exclude_unset=True, mode="json")
184
- )
185
- return Response(status="success", data=[Letter(**r) for r in result])
186
- except Exception as e:
187
- logger.error(e)
188
- raise HTTPException(status_code=500, detail="Error creating letter")
189
-
190
- async def create_letters_by_proposal_id(
191
- self,
192
- letter: CreateLetterRequest,
193
- proposal_id: str = Path(...),
194
- rfp_id: str = Path(...),
195
- ):
196
- try:
197
- async with self.letter_service() as service:
198
- data = letter.model_dump(exclude_unset=True, mode="json")
199
- data["proposal_id"] = proposal_id
200
- result = await service.create_letter(letter=data)
201
- return Response(status="success", data=[Letter(**r) for r in result])
202
- except Exception as e:
203
- logger.error(e)
204
- raise HTTPException(status_code=500, detail="Error creating letter")
205
-
206
- async def update_letter(self, letter: UpdateLetterRequest, id: str = Path(...)):
207
- try:
208
- async with self.letter_service() as service:
209
- result = await service.update_letter(
210
- id=id, letter=letter.model_dump(exclude_unset=True, mode="json")
211
- )
212
- return Response(status="success", data=[Letter(**r) for r in result])
213
- except Exception as e:
214
- logger.error(e)
215
- raise HTTPException(status_code=500, detail="Error updating letter")
216
-
217
- async def update_letters_by_proposal_and_id(
218
- self,
219
- letter: UpdateLetterRequestByProposalAndId,
220
- proposal_id: str = Path(...),
221
- id: str = Path(...),
222
- rfp_id: str = Path(...),
223
- ):
224
- try:
225
- async with self.letter_service() as service:
226
- data = letter.model_dump(exclude_unset=True, mode="json")
227
- data["proposal_id"] = proposal_id
228
- result = await service.update_letter(id=id, letter=data)
229
- return Response(status="success", data=[Letter(**r) for r in result])
230
- except Exception as e:
231
- logger.error(e)
232
- raise HTTPException(status_code=500, detail="Error updating letter")
233
-
234
- async def delete_letter(self, id: str = Path(...)):
235
- try:
236
- async with self.letter_service() as service:
237
- result = await service.delete_letter(id=id)
238
- return DeleteResponse(status="success")
239
- except Exception as e:
240
- logger.error(e)
241
- raise HTTPException(status_code=500, detail="Error deleting letter")
242
-
243
- async def delete_letters_by_proposal_id(
244
- self, proposal_id: str = Path(...), rfp_id: str = Path(...)
245
- ):
246
- try:
247
- async with self.letter_service() as service:
248
- result = await service.delete_letter(proposal_id=proposal_id)
249
- return DeleteResponse(status="success")
250
- except Exception as e:
251
- logger.error(e)
252
- raise HTTPException(status_code=500, detail="Error deleting letter")
253
-
254
- async def delete_letters_by_proposal_and_id(
255
- self, proposal_id: str = Path(...), id: str = Path(...), rfp_id: str = Path(...)
256
- ):
257
- try:
258
- async with self.letter_service() as service:
259
- result = await service.delete_letter(proposal_id=proposal_id, id=id)
260
- return DeleteResponse(status="success")
261
- except Exception as e:
262
- logger.error(e)
263
- raise HTTPException(status_code=500, detail="Error deleting letter")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_proposal_ai_analysis_controller.py DELETED
@@ -1,1783 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path
2
- from pydantic import BaseModel
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
-
9
- from src.services import ProposalAIAnalysisService
10
-
11
- from src.models import QueryType, CreateOrUpdateType, Category
12
-
13
-
14
- class ProposalAIAnalysis(BaseModel):
15
- id: UUID
16
- proposal_id: UUID
17
- ai_score: float
18
- created_at: datetime
19
- updated_at: datetime
20
-
21
-
22
- class ProposalStrength(BaseModel):
23
- id: UUID
24
- proposal_id: UUID
25
- strength_point: str
26
- category: Category
27
- created_at: datetime
28
- updated_at: datetime
29
-
30
-
31
- class ProposalWeakness(BaseModel):
32
- id: UUID
33
- proposal_id: UUID
34
- weakness_point: str
35
- category: Category
36
- created_at: datetime
37
- updated_at: datetime
38
-
39
-
40
- class ProposalRejectionReason(BaseModel):
41
- id: UUID
42
- proposal_id: UUID
43
- reason: str
44
- category: Category
45
- created_at: datetime
46
- updated_at: datetime
47
-
48
-
49
- class ProposalImprovement(BaseModel):
50
- id: UUID
51
- proposal_id: UUID
52
- improvement_point: str
53
- category: Category
54
- created_at: datetime
55
- updated_at: datetime
56
-
57
-
58
- class ProposalAllStrengthsResponse(BaseModel):
59
- status: str
60
- data: Optional[List[str]]
61
-
62
-
63
- class ProposalAllWeaknessesResponse(BaseModel):
64
- status: str
65
- data: Optional[List[str]]
66
-
67
-
68
- class ProposalAllRejectionReasonsResponse(BaseModel):
69
- status: str
70
- data: Optional[List[str]]
71
-
72
-
73
- class ProposalAllImprovementsResponse(BaseModel):
74
- status: str
75
- data: Optional[List[str]]
76
-
77
-
78
- class ProposalAIAnalysisResponse(BaseModel):
79
- status: str
80
- data: Optional[List[ProposalAIAnalysis]]
81
-
82
-
83
- class ProposalStrengthResponse(BaseModel):
84
- status: str
85
- data: Optional[List[ProposalStrength]]
86
-
87
-
88
- class ProposalWeaknessResponse(BaseModel):
89
- status: str
90
- data: Optional[List[ProposalWeakness]]
91
-
92
-
93
- class ProposalRejectionReasonResponse(BaseModel):
94
- status: str
95
- data: Optional[List[ProposalRejectionReason]]
96
-
97
-
98
- class ProposalImprovementResponse(BaseModel):
99
- status: str
100
- data: Optional[List[ProposalImprovement]]
101
-
102
- class Analysis(BaseModel):
103
- strengths: Optional[List[str]] = None
104
- weaknesses: Optional[List[str]] = None
105
- rejection_reasons: Optional[List[str]] = None
106
- improvements: Optional[List[str]] = None
107
-
108
- class ProposalCompleteAnalysis(BaseModel):
109
- ai_score: float
110
- technical: Optional[Analysis] = None
111
- management: Optional[Analysis] = None
112
- past_performance: Optional[Analysis] = None
113
- price: Optional[Analysis] = None
114
-
115
-
116
- class ProposalCompleteAnalysisResponse(BaseModel):
117
- status: str
118
- data: Optional[List[ProposalCompleteAnalysis]]
119
-
120
-
121
- class DeleteResponse(BaseModel):
122
- status: str
123
-
124
-
125
- class ProposalStrengthRequest(BaseModel):
126
- proposal_id: UUID
127
- strength_point: str
128
- category: Category
129
-
130
-
131
- class UpdateProposalStrengthRequest(BaseModel):
132
- proposal_id: Optional[UUID] = None
133
- strength_point: Optional[str] = None
134
- category: Optional[Category] = None
135
-
136
-
137
- class ProposalWeaknessRequest(BaseModel):
138
- proposal_id: UUID
139
- weakness_point: str
140
- category: Category
141
-
142
-
143
- class UpdateProposalWeaknessRequest(BaseModel):
144
- proposal_id: Optional[UUID] = None
145
- weakness_point: Optional[str] = None
146
- category: Optional[Category] = None
147
-
148
-
149
- class ProposalRejectionReasonRequest(BaseModel):
150
- proposal_id: UUID
151
- reason: str
152
- category: Category
153
-
154
-
155
- class UpdateProposalRejectionReasonRequest(BaseModel):
156
- proposal_id: Optional[UUID] = None
157
- reason: Optional[str] = None
158
- category: Optional[Category] = None
159
-
160
-
161
- class ProposalImprovementRequest(BaseModel):
162
- proposal_id: UUID
163
- improvement_point: str
164
- category: Category
165
-
166
-
167
- class UpdateProposalImprovementRequest(BaseModel):
168
- proposal_id: Optional[UUID] = None
169
- improvement_point: Optional[str] = None
170
- category: Optional[Category] = None
171
-
172
-
173
- class CreateAiAnalysisRequest(BaseModel):
174
- proposal_id: UUID
175
- ai_score: float
176
-
177
- class CreateAIAnalysisRequestByProposalId(BaseModel):
178
- ai_score: float
179
-
180
- class UpdateAiAnalysisRequest(BaseModel):
181
- proposal_id: Optional[UUID] = None
182
- ai_score: Optional[float] = None
183
-
184
- class UpdateAIAnalysisRequestByProposalId(BaseModel):
185
- ai_score: Optional[float] = None
186
-
187
- class CreateAllProposalStrengthsRequest(BaseModel):
188
- strength_point: List[str]
189
- category: Category
190
-
191
- class UpdateAllProposalStrengthsRequest(BaseModel):
192
- strength_point: Optional[List[str]] = None
193
-
194
- class CreateAllProposalWeaknessRequest(BaseModel):
195
- weakness_point: List[str]
196
- category: Category
197
-
198
-
199
- class CreateAllProposalRejectionReasonsRequest(BaseModel):
200
- reason: List[str]
201
- category: Category
202
-
203
-
204
- class CreateAllProposalImprovementsRequest(BaseModel):
205
- improvement_point: List[str]
206
- category: Category
207
-
208
-
209
- class CreateAllProposalAnalysisRequest(BaseModel):
210
- ai_score: float
211
- technical: Optional[Analysis] = None
212
- management: Optional[Analysis] = None
213
- past_performance: Optional[Analysis] = None
214
- price: Optional[Analysis] = None
215
-
216
-
217
- class UpdateAllProposalAnalysisRequest(BaseModel):
218
- ai_score: Optional[float] = None
219
- technical: Optional[Analysis] = None
220
- management: Optional[Analysis] = None
221
- past_performance: Optional[Analysis] = None
222
- price: Optional[Analysis] = None
223
-
224
-
225
- class ProposalAIController:
226
- def __init__(self):
227
- self.__service = ProposalAIAnalysisService
228
- self.router = APIRouter()
229
-
230
- # self.router.add_api_route(
231
- # "/proposal-strengths",
232
- # self.get_proposal_strengths,
233
- # methods=["GET"],
234
- # tags=["Proposal Strengths"],
235
- # response_model=ProposalStrengthResponse,
236
- # )
237
- # self.router.add_api_route(
238
- # "/proposal-strengths/{id}",
239
- # self.get_proposal_strengths_by_id,
240
- # methods=["GET"],
241
- # tags=["Proposal Strengths by ID"],
242
- # response_model=ProposalStrengthResponse,
243
- # )
244
- self.router.add_api_route(
245
- "/rfps/{rfp_id}/proposals/{proposal_id}/strengths",
246
- self.get_proposal_strengths_by_proposal_id,
247
- methods=["GET"],
248
- tags=["Proposal Strengths by Proposal ID"],
249
- response_model=ProposalAllStrengthsResponse,
250
- )
251
- self.router.add_api_route(
252
- "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{category}",
253
- self.get_proposal_strengths_by_proposal_and_category,
254
- methods=["GET"],
255
- tags=["Proposal Strengths by Proposal and Category"],
256
- response_model=ProposalAllStrengthsResponse,
257
- )
258
- # self.router.add_api_route(
259
- # "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{id}",
260
- # self.get_proposal_strengths_by_proposal_and_id,
261
- # methods=["GET"],
262
- # tags=["Proposal Strengths by Proposal and ID"],
263
- # response_model=ProposalStrengthResponse,
264
- # )
265
- # self.router.add_api_route(
266
- # "/proposal-strengths",
267
- # self.create_proposal_strengths,
268
- # methods=["POST"],
269
- # tags=["Proposal Strengths"],
270
- # response_model=ProposalStrengthResponse,
271
- # )
272
- self.router.add_api_route(
273
- "/rfps/{rfp_id}/proposals/{proposal_id}/strengths",
274
- self.create_all_proposal_strengths,
275
- methods=["POST"],
276
- tags=["Proposal Strengths by Proposal ID"],
277
- response_model=ProposalAllStrengthsResponse,
278
- )
279
- # self.router.add_api_route(
280
- # "/proposal-strengths/{id}",
281
- # self.update_proposal_strengths,
282
- # methods=["PUT"],
283
- # tags=["Proposal Strengths by ID"],
284
- # response_model=ProposalStrengthResponse,
285
- # )
286
- self.router.add_api_route(
287
- "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{category}",
288
- self.update_all_proposal_strengths,
289
- methods=["PUT"],
290
- tags=["Proposal Strengths by Proposal and Category"],
291
- response_model=ProposalAllStrengthsResponse,
292
- )
293
- # self.router.add_api_route(
294
- # "/proposal-strengths/{id}",
295
- # self.delete_proposal_strength,
296
- # methods=["DELETE"],
297
- # tags=["Proposal Strengths by ID"],
298
- # response_model=DeleteResponse,
299
- # )
300
- self.router.add_api_route(
301
- "/rfps/{rfp_id}/proposals/{proposal_id}/strengths",
302
- self.delete_all_proposal_strengths,
303
- methods=["DELETE"],
304
- tags=["Proposal Strengths by Proposal ID"],
305
- response_model=DeleteResponse,
306
- )
307
- self.router.add_api_route(
308
- "/rfps/{rfp_id}/proposals/{proposal_id}/strengths/{category}",
309
- self.delete_proposal_strengths_by_proposal_and_category,
310
- methods=["DELETE"],
311
- tags=["Proposal Strengths by Proposal and Category"],
312
- response_model=DeleteResponse,
313
- )
314
- # self.router.add_api_route(
315
- # "rfps/{rfp_id}/proposals/{proposal_id}/strengths/{id}",
316
- # self.delete_proposal_strengths_by_proposal_and_id,
317
- # methods=["DELETE"],
318
- # tags=["Proposal Strengths by Proposal and ID"],
319
- # response_model=DeleteResponse,
320
- # )
321
-
322
- # self.router.add_api_route(
323
- # "/proposal-weakness",
324
- # self.get_proposal_weakness,
325
- # methods=["GET"],
326
- # tags=["Proposal Weakness"],
327
- # response_model=ProposalWeaknessResponse,
328
- # )
329
- # self.router.add_api_route(
330
- # "/proposal-weakness/{id}",
331
- # self.get_proposal_weakness_by_id,
332
- # methods=["GET"],
333
- # tags=["Proposal Weakness by ID"],
334
- # response_model=ProposalWeaknessResponse,
335
- # )
336
- self.router.add_api_route(
337
- "/rfps/{rfp_id}/proposals/{proposal_id}/weakness",
338
- self.get_proposal_weakness_by_proposal_id,
339
- methods=["GET"],
340
- tags=["Proposal Weakness by Proposal ID"],
341
- response_model=ProposalAllWeaknessesResponse,
342
- )
343
- # self.router.add_api_route(
344
- # "/rfps/{rfp_id}/proposals/{proposal_id}/weakness/{id}",
345
- # self.get_proposal_weakness_by_proposal_and_id,
346
- # methods=["GET"],
347
- # tags=["Proposal Weakness by Proposal and ID"],
348
- # response_model=ProposalWeaknessResponse,
349
- # )
350
- # self.router.add_api_route(
351
- # "/proposal-weakness",
352
- # self.create_proposal_weakness,
353
- # methods=["POST"],
354
- # tags=["Proposal Weakness"],
355
- # response_model=ProposalWeaknessResponse,
356
- # )
357
- self.router.add_api_route(
358
- "/rfps/{rfp_id}/proposals/{proposal_id}/weakness",
359
- self.create_all_proposal_weakness,
360
- methods=["POST"],
361
- tags=["Proposal Weakness by Proposal ID"],
362
- response_model=ProposalAllWeaknessesResponse,
363
- )
364
- # self.router.add_api_route(
365
- # "/proposal-weakness/{id}",
366
- # self.update_proposal_weakness,
367
- # methods=["PUT"],
368
- # tags=["Proposal Weakness by ID"],
369
- # response_model=ProposalWeaknessResponse,
370
- # )
371
- self.router.add_api_route(
372
- "/rfps/{rfp_id}/proposals/{proposal_id}/weakness",
373
- self.update_all_proposal_weakness,
374
- methods=["PUT"],
375
- tags=["Proposal Weakness by Proposal ID"],
376
- response_model=ProposalAllWeaknessesResponse,
377
- )
378
- # self.router.add_api_route(
379
- # "/proposal-weakness/{id}",
380
- # self.delete_proposal_weakness,
381
- # methods=["DELETE"],
382
- # tags=["Proposal Weakness by ID"],
383
- # response_model=DeleteResponse,
384
- # )
385
- self.router.add_api_route(
386
- "/rfps/{rfp_id}/proposals/{proposal_id}/weakness",
387
- self.delete_all_proposal_weakness,
388
- methods=["DELETE"],
389
- tags=["Proposal Weakness by Proposal ID"],
390
- response_model=DeleteResponse,
391
- )
392
- self.router.add_api_route(
393
- "/rfps/{rfp_id}/proposals/{proposal_id}/weakness/{category}",
394
- self.delete_proposal_weakness_by_proposal_and_category,
395
- methods=["DELETE"],
396
- tags=["Proposal Weakness by Proposal and Category"],
397
- response_model=DeleteResponse,
398
- )
399
- # self.router.add_api_route(
400
- # "/rfps/{rfp_id}/proposals/{proposal_id}/weakness/{id}",
401
- # self.delete_proposal_weakness_by_proposal_and_id,
402
- # methods=["DELETE"],
403
- # tags=["Proposal Weakness by Proposal and ID"],
404
- # response_model=DeleteResponse,
405
- # )
406
-
407
- # self.router.add_api_route(
408
- # "/proposal-rejections",
409
- # self.get_proposal_rejections,
410
- # methods=["GET"],
411
- # tags=["Proposal Rejections"],
412
- # response_model=ProposalRejectionReasonResponse,
413
- # )
414
- # self.router.add_api_route(
415
- # "/proposal-rejections/{id}",
416
- # self.get_proposal_rejections_by_id,
417
- # methods=["GET"],
418
- # tags=["Proposal Rejections by ID"],
419
- # response_model=ProposalRejectionReasonResponse,
420
- # )
421
- self.router.add_api_route(
422
- "/rfps/{rfp_id}/proposals/{proposal_id}/rejections",
423
- self.get_proposal_rejections_by_proposal_id,
424
- methods=["GET"],
425
- tags=["Proposal Rejections by Proposal ID"],
426
- response_model=ProposalAllRejectionReasonsResponse,
427
- )
428
- # self.router.add_api_route(
429
- # "/rfps/{rfp_id}/proposals/{proposal_id}/rejections/{id}",
430
- # self.get_proposal_rejections_by_proposal_and_id,
431
- # methods=["GET"],
432
- # tags=["Proposal Rejections by Proposal and ID"],
433
- # response_model=ProposalRejectionReasonResponse,
434
- # )
435
- # self.router.add_api_route(
436
- # "/proposal-rejections",
437
- # self.create_proposal_rejections,
438
- # methods=["POST"],
439
- # tags=["Proposal Rejections"],
440
- # response_model=ProposalRejectionReasonResponse,
441
- # )
442
- self.router.add_api_route(
443
- "/rfps/{rfp_id}/proposals/{proposal_id}/rejections",
444
- self.create_all_proposal_rejections,
445
- methods=["POST"],
446
- tags=["Proposal Rejections by Proposal ID"],
447
- response_model=ProposalAllRejectionReasonsResponse,
448
- )
449
- # self.router.add_api_route(
450
- # "/proposal-rejections/{id}",
451
- # self.update_proposal_rejections,
452
- # methods=["PUT"],
453
- # tags=["Proposal Rejections by ID"],
454
- # response_model=ProposalRejectionReasonResponse,
455
- # )
456
- self.router.add_api_route(
457
- "/rfps/{rfp_id}/proposals/{proposal_id}/rejections",
458
- self.update_all_proposal_rejections,
459
- methods=["PUT"],
460
- tags=["Proposal Rejections by Proposal ID"],
461
- response_model=ProposalAllRejectionReasonsResponse,
462
- )
463
- # self.router.add_api_route(
464
- # "/proposal-rejections/{id}",
465
- # self.delete_proposal_rejections,
466
- # methods=["DELETE"],
467
- # tags=["Proposal Rejections by ID"],
468
- # response_model=DeleteResponse,
469
- # )
470
- self.router.add_api_route(
471
- "/rfps/{rfp_id}/proposals/{proposal_id}/rejections",
472
- self.delete_all_proposal_rejections,
473
- methods=["DELETE"],
474
- tags=["Proposal Rejections by Proposal ID"],
475
- response_model=DeleteResponse,
476
- )
477
- self.router.add_api_route(
478
- "/rfps/{rfp_id}/proposals/{proposal_id}/rejections/{category}",
479
- self.delete_proposal_rejections_by_proposal_and_category,
480
- methods=["DELETE"],
481
- tags=["Proposal Rejections by Proposal and Category"],
482
- response_model=DeleteResponse,
483
- )
484
- # self.router.add_api_route(
485
- # "/rfps/{rfp_id}/proposals/{proposal_id}/proposal-rejections/{id}",
486
- # self.delete_proposal_rejections_by_proposal_and_id,
487
- # methods=["DELETE"],
488
- # tags=["Proposal Rejections by Proposal and ID"],
489
- # response_model=DeleteResponse,
490
- # )
491
-
492
- # self.router.add_api_route(
493
- # "/proposal-improvements",
494
- # self.get_proposal_improvements,
495
- # methods=["GET"],
496
- # tags=["Proposal Improvements"],
497
- # response_model=ProposalImprovementResponse,
498
- # )
499
- # self.router.add_api_route(
500
- # "/proposal-improvements/{id}",
501
- # self.get_proposal_improvements_by_id,
502
- # methods=["GET"],
503
- # tags=["Proposal Improvements by ID"],
504
- # response_model=ProposalImprovementResponse,
505
- # )
506
- self.router.add_api_route(
507
- "/rfps/{rfp_id}/proposals/{proposal_id}/improvements",
508
- self.get_proposal_improvements_by_proposal_id,
509
- methods=["GET"],
510
- tags=["Proposal Improvements by Proposal ID"],
511
- response_model=ProposalAllImprovementsResponse,
512
- )
513
- # self.router.add_api_route(
514
- # "/proposals/{proposal_id}/proposal-improvements/{id}",
515
- # self.get_proposal_improvements_by_proposal_and_id,
516
- # methods=["GET"],
517
- # tags=["Proposal Improvements by Proposal and ID"],
518
- # response_model=ProposalImprovementResponse,
519
- # )
520
- # self.router.add_api_route(
521
- # "/proposal-improvements",
522
- # self.create_proposal_improvements,
523
- # methods=["POST"],
524
- # tags=["Proposal Improvements"],
525
- # response_model=ProposalImprovementResponse,
526
- # )
527
- self.router.add_api_route(
528
- "/rfps/{rfp_id}/proposals/{proposal_id}/improvements",
529
- self.create_all_proposal_improvements,
530
- methods=["POST"],
531
- tags=["Proposal Improvements by Proposal ID"],
532
- response_model=ProposalAllImprovementsResponse,
533
- )
534
- # self.router.add_api_route(
535
- # "/proposal-improvements/{id}",
536
- # self.update_proposal_improvements,
537
- # methods=["PUT"],
538
- # tags=["Proposal Improvements by ID"],
539
- # response_model=ProposalImprovementResponse,
540
- # )
541
- self.router.add_api_route(
542
- "/rfps/{rfp_id}/proposals/{proposal_id}/improvements",
543
- self.update_all_proposal_improvements,
544
- methods=["PUT"],
545
- tags=["Proposal Improvements by Proposal ID"],
546
- response_model=ProposalAllImprovementsResponse,
547
- )
548
- # self.router.add_api_route(
549
- # "/proposal-improvements/{id}",
550
- # self.delete_proposal_improvements,
551
- # methods=["DELETE"],
552
- # tags=["Proposal Improvements by ID"],
553
- # response_model=DeleteResponse,
554
- # )
555
- self.router.add_api_route(
556
- "/rfps/{rfp_id}/proposals/{proposal_id}/improvements",
557
- self.delete_all_proposal_improvements,
558
- methods=["DELETE"],
559
- tags=["Proposal Improvements by Proposal ID"],
560
- response_model=DeleteResponse,
561
- )
562
- self.router.add_api_route(
563
- "/rfps/{rfp_id}/proposals/{proposal_id}/improvements/{category}",
564
- self.delete_proposal_improvements_by_proposal_and_category,
565
- methods=["DELETE"],
566
- tags=["Proposal Improvements by Proposal and Category"],
567
- response_model=DeleteResponse,
568
- )
569
- # self.router.add_api_route(
570
- # "/proposals/{proposal_id}/proposal-improvements/{id}",
571
- # self.delete_proposal_improvements_by_proposal_and_id,
572
- # methods=["DELETE"],
573
- # tags=["Proposal Improvements by Proposal and ID"],
574
- # response_model=DeleteResponse,
575
- # )
576
-
577
- # self.router.add_api_route(
578
- # "/proposal-ai-score",
579
- # self.get_proposal_ai_score,
580
- # methods=["GET"],
581
- # tags=["Proposal AI Score"],
582
- # response_model=ProposalAIAnalysisResponse,
583
- # )
584
- # self.router.add_api_route(
585
- # "/proposal-ai-score/{id}",
586
- # self.get_proposal_ai_score_by_id,
587
- # methods=["GET"],
588
- # tags=["Proposal AI Score by ID"],
589
- # response_model=ProposalAIAnalysisResponse,
590
- # )
591
- self.router.add_api_route(
592
- "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score",
593
- self.get_proposal_ai_score_by_proposal_id,
594
- methods=["GET"],
595
- tags=["Proposal AI Score by Proposal ID"],
596
- response_model=ProposalAIAnalysisResponse,
597
- )
598
- # self.router.add_api_route(
599
- # "/proposals/{proposal_id}/proposal-ai-score/{id}",
600
- # self.get_proposal_ai_score_by_proposal_and_id,
601
- # methods=["GET"],
602
- # tags=["Proposal AI Score by Proposal and ID"],
603
- # response_model=ProposalAIAnalysisResponse,
604
- # )
605
- # self.router.add_api_route(
606
- # "/proposal-ai-score",
607
- # self.create_proposal_ai_score,
608
- # methods=["POST"],
609
- # tags=["Proposal AI Score"],
610
- # response_model=ProposalAIAnalysisResponse,
611
- # )
612
- self.router.add_api_route(
613
- "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score",
614
- self.create_proposal_ai_score_by_proposal_id,
615
- methods=["POST"],
616
- tags=["Proposal AI Score by Proposal ID"],
617
- response_model=ProposalAIAnalysisResponse,
618
- )
619
- # self.router.add_api_route(
620
- # "/proposal-ai-score/{id}",
621
- # self.update_proposal_ai_score,
622
- # methods=["PUT"],
623
- # tags=["Proposal AI Score by ID"],
624
- # response_model=ProposalAIAnalysisResponse,
625
- # )
626
- self.router.add_api_route(
627
- "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score",
628
- self.update_proposal_ai_score_by_proposal_id,
629
- methods=["PUT"],
630
- tags=["Proposal AI Score by Proposal ID"],
631
- response_model=ProposalAIAnalysisResponse,
632
- )
633
- # self.router.add_api_route(
634
- # "/proposal-ai-score/{id}",
635
- # self.delete_proposal_ai_score,
636
- # methods=["DELETE"],
637
- # tags=["Proposal AI Score by ID"],
638
- # response_model=DeleteResponse,
639
- # )
640
- self.router.add_api_route(
641
- "/rfps/{rfp_id}/proposals/{proposal_id}/ai-score",
642
- self.delete_proposal_ai_score_by_proposal_id,
643
- methods=["DELETE"],
644
- tags=["Proposal AI Score by Proposal ID"],
645
- response_model=DeleteResponse,
646
- )
647
- # self.router.add_api_route(
648
- # "/proposals/{proposal_id}/proposal-ai-score/{id}",
649
- # self.delete_proposal_ai_score_by_proposal_and_id,
650
- # methods=["DELETE"],
651
- # tags=["Proposal AI Score by Proposal and ID"],
652
- # response_model=DeleteResponse,
653
- # )
654
-
655
- self.router.add_api_route(
656
- "/rfps/{rfp_id}/proposals/{proposal_id}/analysis",
657
- self.get_all_proposal_analysis,
658
- methods=["GET"],
659
- tags=["Proposal Complete Analysis"],
660
- response_model=ProposalCompleteAnalysisResponse,
661
- )
662
- self.router.add_api_route(
663
- "/rfps/{rfp_id}/proposals/{proposal_id}/analysis",
664
- self.create_proposal_analysis,
665
- methods=["POST"],
666
- tags=["Proposal Complete Analysis"],
667
- response_model=ProposalCompleteAnalysisResponse,
668
- )
669
- self.router.add_api_route(
670
- "/rfps/{rfp_id}/proposals/{proposal_id}/analysis",
671
- self.update_proposal_analysis,
672
- methods=["PUT"],
673
- tags=["Proposal Complete Analysis"],
674
- response_model=ProposalCompleteAnalysisResponse,
675
- )
676
- self.router.add_api_route(
677
- "/rfps/{rfp_id}/proposals/{proposal_id}/analysis",
678
- self.delete_proposal_analysis,
679
- methods=["DELETE"],
680
- tags=["Proposal Complete Analysis"],
681
- response_model=DeleteResponse,
682
- )
683
-
684
- async def get_proposal_strengths(
685
- self
686
- ):
687
- async with self.__service() as service:
688
- try:
689
- strengths = await service.get_all_data(
690
- query_type=QueryType.STRENGTHS
691
- )
692
- return ProposalStrengthResponse(
693
- status="success", data=[ProposalStrength(**s) for s in strengths]
694
- )
695
- except Exception as e:
696
- logger.exception(e)
697
- raise HTTPException(
698
- status_code=500, detail="Failed to retrieve strengths."
699
- )
700
-
701
- async def get_proposal_strengths_by_id(self, id: UUID = Path(...)):
702
- async with self.__service() as service:
703
- try:
704
- strength = await service.get_all_data(
705
- query_type=QueryType.STRENGTHS, id=id
706
- )
707
- return ProposalStrengthResponse(
708
- status="success", data=[ProposalStrength(**s) for s in strength]
709
- )
710
- except Exception as e:
711
- logger.exception(e)
712
- raise HTTPException(
713
- status_code=500, detail="Failed to retrieve strength."
714
- )
715
-
716
- async def get_proposal_strengths_by_proposal_id(self, rfp_id: UUID = Path(...), proposal_id: UUID = Path(...)):
717
- async with self.__service() as service:
718
- try:
719
- strength = await service.get_all_data(
720
- query_type=QueryType.STRENGTHS, proposal_id=proposal_id
721
- )
722
- return ProposalAllStrengthsResponse(
723
- status="success", data=[s["strength_point"] for s in strength]
724
- )
725
- except Exception as e:
726
- logger.exception(e)
727
- raise HTTPException(
728
- status_code=500, detail="Failed to retrieve strength."
729
- )
730
-
731
- async def get_proposal_strengths_by_proposal_and_category(self, rfp_id: UUID = Path(...), proposal_id: UUID = Path(...), category: Category = Path(...)):
732
- async with self.__service() as service:
733
- try:
734
- strength = await service.get_all_data(
735
- query_type=QueryType.STRENGTHS, proposal_id=proposal_id, category=category
736
- )
737
- return ProposalAllStrengthsResponse(
738
- status="success", data=[s["strength_point"] for s in strength]
739
- )
740
- except Exception as e:
741
- logger.exception(e)
742
- raise HTTPException(
743
- status_code=500, detail="Failed to retrieve strength."
744
- )
745
-
746
- async def get_proposal_strengths_by_proposal_and_id(self, rfp_id: UUID = Path(...), proposal_id: UUID = Path(...), id: UUID = Path(...)):
747
- async with self.__service() as service:
748
- try:
749
- strength = await service.get_all_data(
750
- query_type=QueryType.STRENGTHS, proposal_id=proposal_id, id=id
751
- )
752
- return ProposalStrengthResponse(
753
- status="success", data=[ProposalStrength(**s) for s in strength]
754
- )
755
- except Exception as e:
756
- logger.exception(e)
757
- raise HTTPException(
758
- status_code=500, detail="Failed to retrieve strength."
759
- )
760
-
761
- async def create_proposal_strengths(self, strength: ProposalStrengthRequest):
762
- async with self.__service() as service:
763
- try:
764
- created = await service.create_data(
765
- query_type=QueryType.STRENGTHS,
766
- create_or_update_type=CreateOrUpdateType.ID,
767
- data=strength.model_dump(mode="json"),
768
- )
769
- return ProposalStrengthResponse(
770
- status="success", data=[ProposalStrength(**s) for s in created]
771
- )
772
- except Exception as e:
773
- logger.exception(e)
774
- raise HTTPException(
775
- status_code=500, detail="Failed to create strength."
776
- )
777
-
778
- async def create_all_proposal_strengths(
779
- self, strengths: CreateAllProposalStrengthsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
780
- ):
781
- async with self.__service() as service:
782
- try:
783
- strengths_request = strengths.model_dump(mode="json", exclude_unset=True)
784
- strengths_request["proposal_id"] = proposal_id
785
- created = await service.create_data(
786
- query_type=QueryType.STRENGTHS,
787
- create_or_update_type=CreateOrUpdateType.ALL,
788
- data=strengths_request,
789
- )
790
- return ProposalAllStrengthsResponse(
791
- status="success", data=[s["strength_point"] for s in created]
792
- )
793
- except Exception as e:
794
- logger.exception(e)
795
- raise HTTPException(
796
- status_code=500, detail="Failed to create strengths."
797
- )
798
-
799
- async def update_proposal_strengths(self, strength: UpdateProposalStrengthRequest, id: UUID = Path(...)):
800
- async with self.__service() as service:
801
- try:
802
- strength_request = strength.model_dump(mode="json", exclude_unset=True)
803
- strength_request["id"] = id
804
- updated = await service.update_data(
805
- query_type=QueryType.STRENGTHS,
806
- create_or_update_type=CreateOrUpdateType.ID,
807
- data=strength_request,
808
- )
809
- return ProposalStrengthResponse(
810
- status="success", data=[ProposalStrength(**s) for s in updated]
811
- )
812
- except Exception as e:
813
- logger.exception(e)
814
- raise HTTPException(
815
- status_code=500, detail="Failed to update strength."
816
- )
817
-
818
- async def update_all_proposal_strengths(
819
- self, strengths: UpdateAllProposalStrengthsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Path(...)
820
- ):
821
- async with self.__service() as service:
822
- try:
823
- strengths_request = strengths.model_dump(mode="json", exclude_unset=True)
824
- strengths_request["proposal_id"] = proposal_id
825
- strengths_request["category"] = category
826
- updated = await service.update_data(
827
- query_type=QueryType.STRENGTHS,
828
- create_or_update_type=CreateOrUpdateType.ALL,
829
- data=strengths_request,
830
- )
831
- return ProposalAllStrengthsResponse(
832
- status="success", data=[s["strength_point"] for s in updated]
833
- )
834
- except Exception as e:
835
- logger.exception(e)
836
- raise HTTPException(
837
- status_code=500, detail="Failed to update strengths."
838
- )
839
-
840
- async def delete_proposal_strength(
841
- self, id: UUID = Path(...)
842
- ):
843
- async with self.__service() as service:
844
- try:
845
- result = await service.delete_data(
846
- query_type=QueryType.STRENGTHS, id=id
847
- )
848
- return DeleteResponse(status="success")
849
- except Exception as e:
850
- logger.exception(e)
851
- raise HTTPException(
852
- status_code=500, detail="Failed to delete strength."
853
- )
854
-
855
- async def delete_all_proposal_strengths(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)):
856
- async with self.__service() as service:
857
- try:
858
- result = await service.delete_data(
859
- query_type=QueryType.STRENGTHS, proposal_id=proposal_id
860
- )
861
- return DeleteResponse(status="success")
862
- except Exception as e:
863
- logger.exception(e)
864
- raise HTTPException(
865
- status_code=500, detail="Failed to delete strengths."
866
- )
867
-
868
- async def delete_proposal_strengths_by_proposal_and_category(self, proposal_id: UUID = Path(...), category: Category = Path(...), rfp_id: UUID = Path(...)):
869
- async with self.__service() as service:
870
- try:
871
- result = await service.delete_data(
872
- query_type=QueryType.STRENGTHS, proposal_id=proposal_id, category=category
873
- )
874
- return DeleteResponse(status="success")
875
- except Exception as e:
876
- logger.exception(e)
877
- raise HTTPException(
878
- status_code=500, detail="Failed to delete strength."
879
- )
880
-
881
- async def delete_proposal_strengths_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)):
882
- async with self.__service() as service:
883
- try:
884
- result = await service.delete_data(
885
- query_type=QueryType.STRENGTHS, proposal_id=proposal_id, id=id
886
- )
887
- return DeleteResponse(status="success")
888
- except Exception as e:
889
- logger.exception(e)
890
- raise HTTPException(
891
- status_code=500, detail="Failed to delete strength."
892
- )
893
-
894
- async def get_proposal_weakness(
895
- self
896
- ):
897
- async with self.__service() as service:
898
- try:
899
- weaknesses = await service.get_all_data(
900
- query_type=QueryType.WEAKNESSES
901
- )
902
- return ProposalWeaknessResponse(
903
- status="success", data=[ProposalWeakness(**w) for w in weaknesses]
904
- )
905
- except Exception as e:
906
- logger.exception(e)
907
- raise HTTPException(
908
- status_code=500, detail="Failed to retrieve weaknesses."
909
- )
910
-
911
- async def get_proposal_weakness_by_id(self, id: UUID = Path(...)):
912
- async with self.__service() as service:
913
- try:
914
- weakness = await service.get_all_data(
915
- query_type=QueryType.WEAKNESSES, id=id
916
- )
917
- return ProposalWeaknessResponse(
918
- status="success", data=[ProposalWeakness(**w) for w in weakness]
919
- )
920
- except Exception as e:
921
- logger.exception(e)
922
- raise HTTPException(
923
- status_code=500, detail="Failed to retrieve weakness."
924
- )
925
-
926
- async def get_proposal_weakness_by_proposal_id(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Query(None)):
927
- async with self.__service() as service:
928
- try:
929
- weakness = await service.get_all_data(
930
- query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, category=category
931
- )
932
- return ProposalAllWeaknessesResponse(
933
- status="success", data=[w["weakness_point"] for w in weakness]
934
- )
935
- except Exception as e:
936
- logger.exception(e)
937
- raise HTTPException(
938
- status_code=500, detail="Failed to retrieve weakness."
939
- )
940
-
941
- async def get_proposal_weakness_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)):
942
- async with self.__service() as service:
943
- try:
944
- weakness = await service.get_all_data(
945
- query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, id=id
946
- )
947
- return ProposalWeaknessResponse(
948
- status="success", data=[ProposalWeakness(**w) for w in weakness]
949
- )
950
- except Exception as e:
951
- logger.exception(e)
952
- raise HTTPException(
953
- status_code=500, detail="Failed to retrieve weakness."
954
- )
955
-
956
- async def create_proposal_weakness(self, weakness: ProposalWeaknessRequest):
957
- async with self.__service() as service:
958
- try:
959
- created = await service.create_data(
960
- query_type=QueryType.WEAKNESSES,
961
- create_or_update_type=CreateOrUpdateType.ID,
962
- data=weakness.model_dump(mode="json"),
963
- )
964
- return ProposalWeaknessResponse(
965
- status="success", data=[ProposalWeakness(**w) for w in created]
966
- )
967
- except Exception as e:
968
- logger.exception(e)
969
- raise HTTPException(
970
- status_code=500, detail="Failed to create weakness."
971
- )
972
-
973
- async def create_all_proposal_weakness(
974
- self, weaknesses: CreateAllProposalWeaknessRequest, proposal_id: UUID
975
- ):
976
- async with self.__service() as service:
977
- try:
978
- weakness_request = weaknesses.model_dump(mode="json", exclude_unset=True)
979
- weakness_request["proposal_id"] = proposal_id
980
- created = await service.create_data(
981
- query_type=QueryType.WEAKNESSES,
982
- create_or_update_type=CreateOrUpdateType.ALL,
983
- data=weakness_request,
984
- )
985
- return ProposalAllWeaknessesResponse(
986
- status="success", data=[w["weakness_point"] for w in created]
987
- )
988
- except Exception as e:
989
- logger.exception(e)
990
- raise HTTPException(
991
- status_code=500, detail="Failed to create weaknesses."
992
- )
993
-
994
- async def update_proposal_weakness(self, weakness: UpdateProposalWeaknessRequest, id: UUID = Path(...)):
995
- async with self.__service() as service:
996
- try:
997
- weakness_request = weakness.model_dump(mode="json", exclude_unset=True)
998
- weakness_request["id"] = id
999
- updated = await service.update_data(
1000
- query_type=QueryType.WEAKNESSES,
1001
- create_or_update_type=CreateOrUpdateType.ID,
1002
- data=weakness_request,
1003
- )
1004
- return ProposalWeaknessResponse(
1005
- status="success", data=[ProposalWeakness(**w) for w in updated]
1006
- )
1007
- except Exception as e:
1008
- logger.exception(e)
1009
- raise HTTPException(
1010
- status_code=500, detail="Failed to update weakness."
1011
- )
1012
-
1013
- async def update_all_proposal_weakness(
1014
- self, weaknesses: CreateAllProposalWeaknessRequest, proposal_id: UUID = Path(...)
1015
- ):
1016
- async with self.__service() as service:
1017
- try:
1018
- weakness_request = weaknesses.model_dump(mode="json", exclude_unset=True)
1019
- weakness_request["proposal_id"] = proposal_id
1020
- updated = await service.update_data(
1021
- query_type=QueryType.WEAKNESSES,
1022
- create_or_update_type=CreateOrUpdateType.ALL,
1023
- data=weakness_request,
1024
- )
1025
- return ProposalAllWeaknessesResponse(
1026
- status="success", data=[w["weakness_point"] for w in updated]
1027
- )
1028
- except Exception as e:
1029
- logger.exception(e)
1030
- raise HTTPException(
1031
- status_code=500, detail="Failed to update weaknesses."
1032
- )
1033
-
1034
- async def delete_proposal_weakness(
1035
- self, id: UUID = Path(...)
1036
- ):
1037
- async with self.__service() as service:
1038
- try:
1039
- result = await service.delete_data(
1040
- query_type=QueryType.WEAKNESSES, id=id
1041
- )
1042
- return DeleteResponse(status="success")
1043
- except Exception as e:
1044
- logger.exception(e)
1045
- raise HTTPException(
1046
- status_code=500, detail="Failed to delete weakness."
1047
- )
1048
-
1049
- async def delete_all_proposal_weakness(
1050
- self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1051
- ):
1052
- async with self.__service() as service:
1053
- try:
1054
- result = await service.delete_data(
1055
- query_type=QueryType.WEAKNESSES, proposal_id=proposal_id
1056
- )
1057
- return DeleteResponse(status="success")
1058
- except Exception as e:
1059
- logger.exception(e)
1060
- raise HTTPException(
1061
- status_code=500, detail="Failed to delete weaknesses."
1062
- )
1063
-
1064
- async def delete_proposal_weakness_by_proposal_and_category(self, proposal_id: UUID = Path(...), category: Category = Path(...), rfp_id: UUID = Path(...)):
1065
- async with self.__service() as service:
1066
- try:
1067
- result = await service.delete_data(
1068
- query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, category=category
1069
- )
1070
- return DeleteResponse(status="success")
1071
- except Exception as e:
1072
- logger.exception(e)
1073
- raise HTTPException(
1074
- status_code=500, detail="Failed to delete weakness."
1075
- )
1076
-
1077
- async def delete_proposal_weakness_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)):
1078
- async with self.__service() as service:
1079
- try:
1080
- result = await service.delete_data(
1081
- query_type=QueryType.WEAKNESSES, proposal_id=proposal_id, id=id
1082
- )
1083
- return DeleteResponse(status="success")
1084
- except Exception as e:
1085
- logger.exception(e)
1086
- raise HTTPException(
1087
- status_code=500, detail="Failed to delete weakness."
1088
- )
1089
-
1090
- async def get_proposal_rejections(
1091
- self
1092
- ):
1093
- async with self.__service() as service:
1094
- try:
1095
- rejection_reasons = await service.get_all_data(
1096
- query_type=QueryType.REJECTION_REASONS
1097
- )
1098
- return ProposalRejectionReasonResponse(
1099
- status="success",
1100
- data=[ProposalRejectionReason(**r) for r in rejection_reasons],
1101
- )
1102
- except Exception as e:
1103
- logger.exception(e)
1104
- raise HTTPException(
1105
- status_code=500, detail="Failed to retrieve rejection reasons."
1106
- )
1107
-
1108
- async def get_proposal_rejections_by_id(self, id: UUID = Path(...)):
1109
- async with self.__service() as service:
1110
- try:
1111
- rejection_reason = await service.get_all_data(
1112
- query_type=QueryType.REJECTION_REASONS, id=id
1113
- )
1114
- return ProposalRejectionReasonResponse(
1115
- status="success", data=[ProposalRejectionReason(**r) for r in rejection_reason]
1116
- )
1117
- except Exception as e:
1118
- logger.exception(e)
1119
- raise HTTPException(
1120
- status_code=500, detail="Failed to retrieve rejection reason."
1121
- )
1122
-
1123
- async def get_proposal_rejections_by_proposal_id(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Query(None)):
1124
- async with self.__service() as service:
1125
- try:
1126
- rejection_reason = await service.get_all_data(
1127
- query_type=QueryType.REJECTION_REASONS, proposal_id=proposal_id, category=category
1128
- )
1129
- return ProposalAllRejectionReasonsResponse(
1130
- status="success", data=[r["reason"] for r in rejection_reason]
1131
- )
1132
- except Exception as e:
1133
- logger.exception(e)
1134
- raise HTTPException(
1135
- status_code=500, detail="Failed to retrieve rejection reason."
1136
- )
1137
-
1138
- async def get_proposal_rejections_by_proposal_and_id(self, proposal_id: UUID = Path(...), id: UUID = Path(...), rfp_id: UUID = Path(...)):
1139
- async with self.__service() as service:
1140
- try:
1141
- rejection_reason = await service.get_all_data(
1142
- query_type=QueryType.REJECTION_REASONS, proposal_id=proposal_id, id=id
1143
- )
1144
- return ProposalRejectionReasonResponse(
1145
- status="success", data=[ProposalRejectionReason(**r) for r in rejection_reason]
1146
- )
1147
- except Exception as e:
1148
- logger.exception(e)
1149
- raise HTTPException(
1150
- status_code=500, detail="Failed to retrieve rejection reason."
1151
- )
1152
-
1153
- async def create_proposal_rejections(
1154
- self, rejection_reason: ProposalRejectionReasonRequest
1155
- ):
1156
- async with self.__service() as service:
1157
- try:
1158
- created = await service.create_data(
1159
- query_type=QueryType.REJECTION_REASONS,
1160
- create_or_update_type=CreateOrUpdateType.ID,
1161
- data=rejection_reason.model_dump(mode="json"),
1162
- )
1163
- return ProposalRejectionReasonResponse(
1164
- status="success",
1165
- data=[ProposalRejectionReason(**r) for r in created],
1166
- )
1167
- except Exception as e:
1168
- logger.exception(e)
1169
- raise HTTPException(
1170
- status_code=500, detail="Failed to create rejection reason."
1171
- )
1172
-
1173
- async def create_all_proposal_rejections(
1174
- self, rejection_reasons: CreateAllProposalRejectionReasonsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1175
- ):
1176
- async with self.__service() as service:
1177
- try:
1178
- rejection_reasons_data = rejection_reasons.model_dump(mode="json", exclude_unset=True)
1179
- rejection_reasons_data["proposal_id"] = proposal_id
1180
- created = await service.create_data(
1181
- query_type=QueryType.REJECTION_REASONS,
1182
- create_or_update_type=CreateOrUpdateType.ALL,
1183
- data=rejection_reasons_data,
1184
- )
1185
- return ProposalAllRejectionReasonsResponse(
1186
- status="success",
1187
- data=[r["reason"] for r in created],
1188
- )
1189
- except Exception as e:
1190
- logger.exception(e)
1191
- raise HTTPException(
1192
- status_code=500, detail="Failed to create rejection reasons."
1193
- )
1194
-
1195
- async def update_proposal_rejections(
1196
- self, rejection_reason: UpdateProposalRejectionReasonRequest, id: UUID = Path(...)
1197
- ):
1198
- async with self.__service() as service:
1199
- try:
1200
- rejection_reason_data = rejection_reason.model_dump(mode="json", exclude_unset=True)
1201
- rejection_reason_data["id"] = id
1202
- updated = await service.update_data(
1203
- query_type=QueryType.REJECTION_REASONS,
1204
- create_or_update_type=CreateOrUpdateType.ID,
1205
- data=rejection_reason_data,
1206
- )
1207
- return ProposalRejectionReasonResponse(
1208
- status="success",
1209
- data=[ProposalRejectionReason(**r) for r in updated],
1210
- )
1211
- except Exception as e:
1212
- logger.exception(e)
1213
- raise HTTPException(
1214
- status_code=500, detail="Failed to update rejection reason."
1215
- )
1216
-
1217
- async def update_all_proposal_rejections(
1218
- self, rejection_reasons: CreateAllProposalRejectionReasonsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1219
- ):
1220
- async with self.__service() as service:
1221
- try:
1222
- rejection_reasons_data = rejection_reasons.model_dump(mode="json", exclude_unset=True)
1223
- rejection_reasons_data["proposal_id"] = proposal_id
1224
- updated = await service.update_data(
1225
- query_type=QueryType.REJECTION_REASONS,
1226
- create_or_update_type=CreateOrUpdateType.ALL,
1227
- data=rejection_reasons_data,
1228
- )
1229
- return ProposalAllRejectionReasonsResponse(
1230
- status="success",
1231
- data=[r["reason"] for r in updated],
1232
- )
1233
- except Exception as e:
1234
- logger.exception(e)
1235
- raise HTTPException(
1236
- status_code=500, detail="Failed to update rejection reasons."
1237
- )
1238
-
1239
- async def delete_proposal_rejections(
1240
- self, id: UUID = Path(...)
1241
- ):
1242
- async with self.__service() as service:
1243
- try:
1244
- result = await service.delete_data(
1245
- query_type=QueryType.REJECTION_REASONS,
1246
- id=id
1247
- )
1248
- return DeleteResponse(status="success")
1249
- except Exception as e:
1250
- logger.exception(e)
1251
- raise HTTPException(
1252
- status_code=500, detail="Failed to delete rejection reason."
1253
- )
1254
-
1255
- async def delete_all_proposal_rejections(
1256
- self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1257
- ):
1258
- async with self.__service() as service:
1259
- try:
1260
- result = await service.delete_data(
1261
- query_type=QueryType.REJECTION_REASONS,
1262
- proposal_id=proposal_id
1263
- )
1264
- return DeleteResponse(status="success")
1265
- except Exception as e:
1266
- logger.exception(e)
1267
- raise HTTPException(
1268
- status_code=500, detail="Failed to delete rejection reasons."
1269
- )
1270
-
1271
- async def delete_proposal_rejections_by_proposal_and_category(
1272
- self, proposal_id: UUID = Path(...), category: str = Path(...), rfp_id: UUID = Path(...)
1273
- ):
1274
- async with self.__service() as service:
1275
- try:
1276
- result = await service.delete_data(
1277
- query_type=QueryType.REJECTION_REASONS,
1278
- category=category,
1279
- proposal_id=proposal_id
1280
- )
1281
- return DeleteResponse(status="success")
1282
- except Exception as e:
1283
- logger.exception(e)
1284
- raise HTTPException(
1285
- status_code=500, detail="Failed to delete rejection reason."
1286
- )
1287
-
1288
- async def delete_proposal_rejections_by_proposal_and_id(
1289
- self, proposal_id: UUID = Path(...), id: UUID = Path(...)
1290
- ):
1291
- async with self.__service() as service:
1292
- try:
1293
- result = await service.delete_data(
1294
- query_type=QueryType.REJECTION_REASONS,
1295
- id=id,
1296
- proposal_id=proposal_id
1297
- )
1298
- return DeleteResponse(status="success")
1299
- except Exception as e:
1300
- logger.exception(e)
1301
- raise HTTPException(
1302
- status_code=500, detail="Failed to delete rejection reason."
1303
- )
1304
-
1305
- async def get_proposal_improvements(
1306
- self
1307
- ):
1308
- async with self.__service() as service:
1309
- try:
1310
- improvements = await service.get_all_data(
1311
- query_type=QueryType.IMPROVEMENTS
1312
- )
1313
- return ProposalImprovementResponse(
1314
- status="success",
1315
- data=[ProposalImprovement(**r) for r in improvements],
1316
- )
1317
- except Exception as e:
1318
- logger.exception(e)
1319
- raise HTTPException(
1320
- status_code=500, detail="Failed to retrieve improvements."
1321
- )
1322
-
1323
- async def get_proposal_improvements_by_id(
1324
- self, id: UUID = Path(...)
1325
- ):
1326
- async with self.__service() as service:
1327
- try:
1328
- improvements = await service.get_all_data(
1329
- query_type=QueryType.IMPROVEMENTS,
1330
- id=id
1331
- )
1332
- return ProposalImprovementResponse(
1333
- status="success",
1334
- data=[ProposalImprovement(**r) for r in improvements],
1335
- )
1336
- except Exception as e:
1337
- logger.exception(e)
1338
- raise HTTPException(
1339
- status_code=500, detail="Failed to retrieve improvements."
1340
- )
1341
-
1342
- async def get_proposal_improvements_by_proposal_id(
1343
- self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...), category: Category = Query(None)
1344
- ):
1345
- async with self.__service() as service:
1346
- try:
1347
- improvements = await service.get_all_data(
1348
- query_type=QueryType.IMPROVEMENTS,
1349
- proposal_id=proposal_id,
1350
- category=category
1351
- )
1352
- return ProposalAllImprovementsResponse(
1353
- status="success",
1354
- data=[r["improvement_point"] for r in improvements],
1355
- )
1356
- except Exception as e:
1357
- logger.exception(e)
1358
- raise HTTPException(
1359
- status_code=500, detail="Failed to retrieve improvements."
1360
- )
1361
-
1362
- async def get_proposal_improvements_by_proposal_and_id(
1363
- self, proposal_id: UUID = Path(...), id: UUID = Path(...)
1364
- ):
1365
- async with self.__service() as service:
1366
- try:
1367
- improvements = await service.get_all_data(
1368
- query_type=QueryType.IMPROVEMENTS,
1369
- id=id,
1370
- proposal_id=proposal_id
1371
- )
1372
- return ProposalImprovementResponse(
1373
- status="success",
1374
- data=[ProposalImprovement(**r) for r in improvements],
1375
- )
1376
- except Exception as e:
1377
- logger.exception(e)
1378
- raise HTTPException(
1379
- status_code=500, detail="Failed to retrieve improvements."
1380
- )
1381
-
1382
- async def create_proposal_improvements(
1383
- self, improvement: ProposalImprovementRequest
1384
- ):
1385
- async with self.__service() as service:
1386
- try:
1387
- created = await service.create_data(
1388
- query_type=QueryType.IMPROVEMENTS,
1389
- create_or_update_type=CreateOrUpdateType.ID,
1390
- data=improvement.model_dump(mode="json"),
1391
- )
1392
- return ProposalImprovementResponse(
1393
- status="success", data=[ProposalImprovement(**r) for r in created]
1394
- )
1395
- except Exception as e:
1396
- logger.exception(e)
1397
- raise HTTPException(
1398
- status_code=500, detail="Failed to create improvement."
1399
- )
1400
-
1401
- async def create_all_proposal_improvements(
1402
- self, improvements: CreateAllProposalImprovementsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1403
- ):
1404
- async with self.__service() as service:
1405
- try:
1406
- data = improvements.model_dump(mode="json", exclude_unset=True)
1407
- data["proposal_id"] = proposal_id
1408
- created = await service.create_data(
1409
- query_type=QueryType.IMPROVEMENTS,
1410
- create_or_update_type=CreateOrUpdateType.ALL,
1411
- data=data,
1412
- )
1413
- return ProposalAllImprovementsResponse(
1414
- status="success", data=[r["improvement_point"] for r in created]
1415
- )
1416
- except Exception as e:
1417
- logger.exception(e)
1418
- raise HTTPException(
1419
- status_code=500, detail="Failed to create improvements."
1420
- )
1421
-
1422
- async def update_proposal_improvements(
1423
- self, improvement: UpdateProposalImprovementRequest, id: UUID = Path(...)
1424
- ):
1425
- async with self.__service() as service:
1426
- try:
1427
- data = improvement.model_dump(mode="json", exclude_unset=True)
1428
- data["id"] = id
1429
- updated = await service.update_data(
1430
- query_type=QueryType.IMPROVEMENTS,
1431
- create_or_update_type=CreateOrUpdateType.ID,
1432
- data=data,
1433
- )
1434
- return ProposalImprovementResponse(
1435
- status="success", data=[ProposalImprovement(**r) for r in updated]
1436
- )
1437
- except Exception as e:
1438
- logger.exception(e)
1439
- raise HTTPException(
1440
- status_code=500, detail="Failed to update improvement."
1441
- )
1442
-
1443
- async def update_all_proposal_improvements(
1444
- self, improvements: CreateAllProposalImprovementsRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1445
- ):
1446
- async with self.__service() as service:
1447
- try:
1448
- data = improvements.model_dump(mode="json", exclude_unset=True)
1449
- data["proposal_id"] = proposal_id
1450
- updated = await service.update_data(
1451
- query_type=QueryType.IMPROVEMENTS,
1452
- create_or_update_type=CreateOrUpdateType.ALL,
1453
- data=data,
1454
- )
1455
- return ProposalAllImprovementsResponse(
1456
- status="success", data=[r["improvement_point"] for r in updated]
1457
- )
1458
- except Exception as e:
1459
- logger.exception(e)
1460
- raise HTTPException(
1461
- status_code=500, detail="Failed to update improvements."
1462
- )
1463
-
1464
- async def delete_proposal_improvements(
1465
- self, id: UUID = Path(...)
1466
- ):
1467
- async with self.__service() as service:
1468
- try:
1469
- result = await service.delete_data(
1470
- query_type=QueryType.IMPROVEMENTS, id=id
1471
- )
1472
- return DeleteResponse(status="success")
1473
- except Exception as e:
1474
- logger.exception(e)
1475
- raise HTTPException(
1476
- status_code=500, detail="Failed to delete improvement."
1477
- )
1478
-
1479
- async def delete_all_proposal_improvements(
1480
- self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1481
- ):
1482
- async with self.__service() as service:
1483
- try:
1484
- result = await service.delete_data(
1485
- query_type=QueryType.IMPROVEMENTS, proposal_id=proposal_id
1486
- )
1487
- return DeleteResponse(status="success")
1488
- except Exception as e:
1489
- logger.exception(e)
1490
- raise HTTPException(
1491
- status_code=500, detail="Failed to delete improvements."
1492
- )
1493
-
1494
- async def delete_proposal_improvements_by_proposal_and_category(
1495
- self, category: str = Path(...), proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1496
- ):
1497
- async with self.__service() as service:
1498
- try:
1499
- result = await service.delete_data(
1500
- query_type=QueryType.IMPROVEMENTS, category=category, proposal_id=proposal_id
1501
- )
1502
- return DeleteResponse(status="success")
1503
- except Exception as e:
1504
- logger.exception(e)
1505
- raise HTTPException(
1506
- status_code=500, detail="Failed to delete improvement."
1507
- )
1508
-
1509
- async def delete_proposal_improvements_by_proposal_and_id(
1510
- self, id: UUID = Path(...), proposal_id: UUID = Path(...)
1511
- ):
1512
- async with self.__service() as service:
1513
- try:
1514
- result = await service.delete_data(
1515
- query_type=QueryType.IMPROVEMENTS, id=id, proposal_id=proposal_id
1516
- )
1517
- return DeleteResponse(status="success")
1518
- except Exception as e:
1519
- logger.exception(e)
1520
- raise HTTPException(
1521
- status_code=500, detail="Failed to delete improvement."
1522
- )
1523
-
1524
- async def get_proposal_ai_score(
1525
- self
1526
- ):
1527
- async with self.__service() as service:
1528
- try:
1529
- scores = await service.get_all_data(
1530
- query_type=QueryType.AI_SCORE
1531
- )
1532
- return ProposalAIAnalysisResponse(
1533
- status="success", data=[ProposalAIAnalysis(**r) for r in scores]
1534
- )
1535
- except Exception as e:
1536
- logger.exception(e)
1537
- raise HTTPException(
1538
- status_code=500, detail="Failed to retrieve AI scores."
1539
- )
1540
-
1541
- async def get_proposal_ai_score_by_id(
1542
- self, id: UUID = Path(...)
1543
- ):
1544
- async with self.__service() as service:
1545
- try:
1546
- scores = await service.get_all_data(
1547
- query_type=QueryType.AI_SCORE, id=id
1548
- )
1549
- return ProposalAIAnalysisResponse(
1550
- status="success", data=[ProposalAIAnalysis(**r) for r in scores]
1551
- )
1552
- except Exception as e:
1553
- logger.exception(e)
1554
- raise HTTPException(
1555
- status_code=500, detail="Failed to retrieve AI scores."
1556
- )
1557
-
1558
- async def get_proposal_ai_score_by_proposal_id(
1559
- self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1560
- ):
1561
- async with self.__service() as service:
1562
- try:
1563
- scores = await service.get_all_data(
1564
- query_type=QueryType.AI_SCORE, proposal_id=proposal_id
1565
- )
1566
- return ProposalAIAnalysisResponse(
1567
- status="success", data=[ProposalAIAnalysis(**r) for r in scores]
1568
- )
1569
- except Exception as e:
1570
- logger.exception(e)
1571
- raise HTTPException(
1572
- status_code=500, detail="Failed to retrieve AI scores."
1573
- )
1574
-
1575
- async def get_proposal_ai_score_by_proposal_and_id(
1576
- self, id: UUID = Path(...), proposal_id: UUID = Path(...)
1577
- ):
1578
- async with self.__service() as service:
1579
- try:
1580
- scores = await service.get_all_data(
1581
- query_type=QueryType.AI_SCORE, id=id, proposal_id=proposal_id
1582
- )
1583
- return ProposalAIAnalysisResponse(
1584
- status="success", data=[ProposalAIAnalysis(**r) for r in scores]
1585
- )
1586
- except Exception as e:
1587
- logger.exception(e)
1588
- raise HTTPException(
1589
- status_code=500, detail="Failed to retrieve AI scores."
1590
- )
1591
-
1592
- async def create_proposal_ai_score(self, score: CreateAiAnalysisRequest):
1593
- async with self.__service() as service:
1594
- try:
1595
- created = await service.create_data(
1596
- query_type=QueryType.AI_SCORE,
1597
- create_or_update_type=CreateOrUpdateType.ID,
1598
- data=score.model_dump(mode="json"),
1599
- )
1600
- return ProposalAIAnalysisResponse(
1601
- status="success", data=[ProposalAIAnalysis(**r) for r in created]
1602
- )
1603
- except Exception as e:
1604
- logger.exception(e)
1605
- raise HTTPException(
1606
- status_code=500, detail="Failed to create AI score."
1607
- )
1608
-
1609
- async def create_proposal_ai_score_by_proposal_id(self, score: CreateAIAnalysisRequestByProposalId, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)):
1610
- async with self.__service() as service:
1611
- try:
1612
- data = score.model_dump(mode="json", exclude_unset=True)
1613
- data["proposal_id"] = proposal_id
1614
- created = await service.create_data(
1615
- query_type=QueryType.AI_SCORE,
1616
- create_or_update_type=CreateOrUpdateType.ID,
1617
- data=data,
1618
- )
1619
- return ProposalAIAnalysisResponse(
1620
- status="success", data=[ProposalAIAnalysis(**r) for r in created]
1621
- )
1622
- except Exception as e:
1623
- logger.exception(e)
1624
- raise HTTPException(
1625
- status_code=500, detail="Failed to create AI score."
1626
- )
1627
-
1628
- async def update_proposal_ai_score(self, score: UpdateAiAnalysisRequest, id: UUID = Path(...)):
1629
- async with self.__service() as service:
1630
- try:
1631
- if id:
1632
- data = score.model_dump(mode="json", exclude_unset=True)
1633
- data["id"] = id
1634
- updated = await service.update_data(
1635
- query_type=QueryType.AI_SCORE,
1636
- create_or_update_type=CreateOrUpdateType.ID,
1637
- data=data,
1638
- )
1639
- return ProposalAIAnalysisResponse(
1640
- status="success", data=[ProposalAIAnalysis(**r) for r in updated]
1641
- )
1642
- except Exception as e:
1643
- logger.exception(e)
1644
- raise HTTPException(
1645
- status_code=500, detail="Failed to update AI score."
1646
- )
1647
-
1648
- async def update_proposal_ai_score_by_proposal_id(self, score: UpdateAIAnalysisRequestByProposalId, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)):
1649
- async with self.__service() as service:
1650
- try:
1651
- data = score.model_dump(mode="json", exclude_unset=True)
1652
- data["proposal_id"] = proposal_id
1653
- updated = await service.update_data(
1654
- query_type=QueryType.AI_SCORE,
1655
- create_or_update_type=CreateOrUpdateType.ALL,
1656
- data=data,
1657
- )
1658
- return ProposalAIAnalysisResponse(
1659
- status="success", data=[ProposalAIAnalysis(**r) for r in updated]
1660
- )
1661
- except Exception as e:
1662
- logger.exception(e)
1663
- raise HTTPException(
1664
- status_code=500, detail="Failed to update AI score."
1665
- )
1666
-
1667
- async def delete_proposal_ai_score(
1668
- self, id: UUID = Path(...)
1669
- ):
1670
- async with self.__service() as service:
1671
- try:
1672
- result = await service.delete_data(
1673
- query_type=QueryType.AI_SCORE, id=id
1674
- )
1675
- return DeleteResponse(status="success")
1676
- except Exception as e:
1677
- logger.exception(e)
1678
- raise HTTPException(
1679
- status_code=500, detail="Failed to delete AI score."
1680
- )
1681
-
1682
- async def delete_proposal_ai_score_by_proposal_id(
1683
- self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1684
- ):
1685
- async with self.__service() as service:
1686
- try:
1687
- result = await service.delete_data(
1688
- query_type=QueryType.AI_SCORE, proposal_id=proposal_id
1689
- )
1690
- return DeleteResponse(status="success")
1691
- except Exception as e:
1692
- logger.exception(e)
1693
- raise HTTPException(
1694
- status_code=500, detail="Failed to delete AI score."
1695
- )
1696
-
1697
- async def delete_proposal_ai_score_by_proposal_and_id(
1698
- self, id: UUID = Path(...), proposal_id: UUID = Path(...)
1699
- ):
1700
- async with self.__service() as service:
1701
- try:
1702
- result = await service.delete_data(
1703
- query_type=QueryType.AI_SCORE, id=id, proposal_id=proposal_id
1704
- )
1705
- return DeleteResponse(status="success")
1706
- except Exception as e:
1707
- logger.exception(e)
1708
- raise HTTPException(
1709
- status_code=500, detail="Failed to delete AI score."
1710
- )
1711
-
1712
- async def get_all_proposal_analysis(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)):
1713
- async with self.__service() as service:
1714
- try:
1715
- analysis = await service.get_all_data(
1716
- query_type=QueryType.ALL, proposal_id=proposal_id
1717
- )
1718
- return ProposalCompleteAnalysisResponse(
1719
- status="success",
1720
- data=[ProposalCompleteAnalysis(**analysis)],
1721
- )
1722
- except Exception as e:
1723
- logger.exception(e)
1724
- raise HTTPException(
1725
- status_code=500, detail="Failed to retrieve analysis."
1726
- )
1727
-
1728
- async def create_proposal_analysis(
1729
- self, analysis: CreateAllProposalAnalysisRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1730
- ):
1731
- async with self.__service() as service:
1732
- try:
1733
- data = analysis.model_dump(mode="json", exclude_unset=True)
1734
- data["proposal_id"] = proposal_id
1735
- created = await service.create_data(
1736
- query_type=QueryType.ALL,
1737
- create_or_update_type=CreateOrUpdateType.ALL,
1738
- data=data,
1739
- )
1740
- return ProposalCompleteAnalysisResponse(
1741
- status="success",
1742
- data=[ProposalCompleteAnalysis(**created)],
1743
- )
1744
- except Exception as e:
1745
- logger.exception(e)
1746
- raise HTTPException(
1747
- status_code=500, detail="Failed to create analysis."
1748
- )
1749
-
1750
- async def update_proposal_analysis(
1751
- self, analysis: UpdateAllProposalAnalysisRequest, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)
1752
- ):
1753
- async with self.__service() as service:
1754
- try:
1755
- data = analysis.model_dump(mode="json", exclude_unset=True)
1756
- data["proposal_id"] = proposal_id
1757
- updated = await service.update_data(
1758
- query_type=QueryType.ALL,
1759
- create_or_update_type=CreateOrUpdateType.ALL,
1760
- data=data,
1761
- )
1762
- return ProposalCompleteAnalysisResponse(
1763
- status="success",
1764
- data=[ProposalCompleteAnalysis(**updated)],
1765
- )
1766
- except Exception as e:
1767
- logger.exception(e)
1768
- raise HTTPException(
1769
- status_code=500, detail="Failed to update analysis."
1770
- )
1771
-
1772
- async def delete_proposal_analysis(self, proposal_id: UUID = Path(...), rfp_id: UUID = Path(...)):
1773
- async with self.__service() as service:
1774
- try:
1775
- result = await service.delete_data(
1776
- query_type=QueryType.ALL, proposal_id=proposal_id
1777
- )
1778
- return DeleteResponse(status="success")
1779
- except Exception as e:
1780
- logger.exception(e)
1781
- raise HTTPException(
1782
- status_code=500, detail="Failed to delete analysis."
1783
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_proposal_controller.py DELETED
@@ -1,338 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path
2
- from pydantic import BaseModel, field_validator
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
- from src.services import ProposalService
9
- from src.models import ProposalStatus, GateCriteria
10
-
11
-
12
- class Proposal(BaseModel):
13
- id: UUID
14
- rfp_id: UUID
15
- name: str
16
- tep: str
17
- gate_criteria: GateCriteria
18
- status: ProposalStatus
19
- ai_score: Optional[float] = None
20
- final_score: Optional[float] = None
21
- created_at: datetime
22
- updated_at: datetime
23
-
24
- @field_validator("ai_score", "final_score")
25
- @classmethod
26
- def round_to_two_decimals(cls, v):
27
- if v is not None:
28
- return round(v, 2)
29
- return v
30
-
31
- class ProposalRequest(BaseModel):
32
- rfp_id: UUID
33
- name: str
34
- tep: str
35
- gate_criteria: GateCriteria
36
- status: ProposalStatus
37
- ai_score: Optional[float] = None
38
- final_score: Optional[float] = None
39
-
40
-
41
- class CreateProposalRequest(BaseModel):
42
- name: str
43
- tep: str
44
- gate_criteria: GateCriteria
45
- status: ProposalStatus
46
- ai_score: Optional[float] = None
47
- final_score: Optional[float] = None
48
-
49
-
50
- class ProposalUpdateRequest(BaseModel):
51
- rfp_id: Optional[UUID] = None
52
- name: Optional[str] = None
53
- tep: Optional[str] = None
54
- gate_criteria: Optional[GateCriteria] = None
55
- status: Optional[ProposalStatus] = None
56
- ai_score: Optional[float] = None
57
- final_score: Optional[float] = None
58
-
59
-
60
- class UpdateProposalRequest(BaseModel):
61
- name: Optional[str] = None
62
- tep: Optional[str] = None
63
- gate_criteria: Optional[GateCriteria] = None
64
- status: Optional[ProposalStatus] = None
65
- ai_score: Optional[float] = None
66
- final_score: Optional[float] = None
67
-
68
-
69
- class ProposalDeleteResponse(BaseModel):
70
- status: str
71
-
72
-
73
- class ResponseProposal(BaseModel):
74
- status: str
75
- data: Optional[List[Proposal]]
76
-
77
-
78
- class ProposalController:
79
- def __init__(self):
80
- self.__proposal_service = ProposalService
81
- self.router = APIRouter()
82
- # self.router.add_api_route(
83
- # "/proposals",
84
- # self.get_proposals,
85
- # methods=["GET"],
86
- # response_model=ResponseProposal,
87
- # tags=["Proposals"],
88
- # )
89
- # self.router.add_api_route(
90
- # "/proposals/{proposal_id}",
91
- # self.get_proposals_by_id,
92
- # methods=["GET"],
93
- # response_model=ResponseProposal,
94
- # tags=["Proposals by ID"],
95
- # )
96
- self.router.add_api_route(
97
- "/rfps/{rfp_id}/proposals",
98
- self.get_proposals_by_rfp_id,
99
- methods=["GET"],
100
- response_model=ResponseProposal,
101
- tags=["Proposals by RFP ID"],
102
- )
103
- self.router.add_api_route(
104
- "/rfps/{rfp_id}/proposals/{proposal_id}",
105
- self.get_proposals_by_proposal_and_rfp_id,
106
- methods=["GET"],
107
- response_model=ResponseProposal,
108
- tags=["Proposals by Proposal and RFP ID"],
109
- )
110
- # self.router.add_api_route(
111
- # "/proposals",
112
- # self.create_proposal,
113
- # methods=["POST"],
114
- # response_model=ResponseProposal,
115
- # tags=["Proposals"],
116
- # )
117
- self.router.add_api_route(
118
- "/rfps/{rfp_id}/proposals",
119
- self.create_proposal_by_rfp_id,
120
- methods=["POST"],
121
- response_model=ResponseProposal,
122
- tags=["Proposals by RFP ID"],
123
- )
124
- # self.router.add_api_route(
125
- # "/proposals/{proposal_id}",
126
- # self.update_proposal,
127
- # methods=["PUT"],
128
- # response_model=ResponseProposal,
129
- # tags=["Proposals by ID"],
130
- # )
131
- self.router.add_api_route(
132
- "/rfps/{rfp_id}/proposals/{proposal_id}",
133
- self.update_proposal_by_proposal_and_rfp_id,
134
- methods=["PUT"],
135
- response_model=ResponseProposal,
136
- tags=["Proposals by Proposal and RFP ID"],
137
- )
138
- # self.router.add_api_route(
139
- # "/proposals/{proposal_id}",
140
- # self.delete_proposal,
141
- # methods=["DELETE"],
142
- # response_model=ProposalDeleteResponse,
143
- # tags=["Proposals by ID"],
144
- # )
145
- self.router.add_api_route(
146
- "/rfps/{rfp_id}/proposals",
147
- self.delete_proposals_by_rfp_id,
148
- methods=["DeLETE"],
149
- response_model=ProposalDeleteResponse,
150
- tags=["Proposals by RFP ID"],
151
- )
152
- self.router.add_api_route(
153
- "/rfps/{rfp_id}/proposals/{proposal_id}",
154
- self.delete_proposals_by_proposal_and_rfp_id,
155
- methods=["DeLETE"],
156
- response_model=ProposalDeleteResponse,
157
- tags=["Proposals by Proposal and RFP ID"],
158
- )
159
-
160
- async def get_proposals(self):
161
- async with self.__proposal_service() as service:
162
- try:
163
- proposals = await service.get_proposals()
164
- return ResponseProposal(
165
- status="success", data=[Proposal(**p) for p in proposals]
166
- )
167
- except Exception as e:
168
- logger.exception(e)
169
- raise HTTPException(
170
- status_code=500, detail="Failed to retrieve proposals."
171
- )
172
-
173
- async def get_proposals_by_id(
174
- self,
175
- proposal_id: str = Path(...),
176
- ):
177
- async with self.__proposal_service() as service:
178
- try:
179
- proposals = await service.get_proposals(proposal_id=proposal_id)
180
- return ResponseProposal(
181
- status="success", data=[Proposal(**p) for p in proposals]
182
- )
183
- except Exception as e:
184
- logger.exception(e)
185
- raise HTTPException(
186
- status_code=500, detail="Failed to retrieve proposals."
187
- )
188
-
189
- async def get_proposals_by_rfp_id(
190
- self,
191
- rfp_id: str = Path(...),
192
- ):
193
- async with self.__proposal_service() as service:
194
- try:
195
- proposals = await service.get_proposals(rfp_id=rfp_id)
196
- return ResponseProposal(
197
- status="success", data=[Proposal(**p) for p in proposals]
198
- )
199
- except Exception as e:
200
- logger.exception(e)
201
- raise HTTPException(
202
- status_code=500, detail="Failed to retrieve proposals."
203
- )
204
-
205
- async def get_proposals_by_proposal_and_rfp_id(
206
- self,
207
- rfp_id: str = Path(...),
208
- proposal_id: str = Path(...),
209
- ):
210
- async with self.__proposal_service() as service:
211
- try:
212
- proposals = await service.get_proposals(
213
- rfp_id=rfp_id, proposal_id=proposal_id
214
- )
215
- return ResponseProposal(
216
- status="success", data=[Proposal(**p) for p in proposals]
217
- )
218
- except Exception as e:
219
- logger.exception(e)
220
- raise HTTPException(
221
- status_code=500, detail="Failed to retrieve proposals."
222
- )
223
-
224
- async def create_proposal(self, proposal: ProposalRequest):
225
- async with self.__proposal_service() as service:
226
- try:
227
- new_proposal = await service.create_proposal(
228
- proposal.model_dump(mode="json")
229
- )
230
- return ResponseProposal(
231
- status="success", data=[Proposal(**new_proposal)]
232
- )
233
- except Exception as e:
234
- logger.exception(e)
235
- raise HTTPException(
236
- status_code=500, detail="Failed to create proposal."
237
- )
238
-
239
- async def create_proposal_by_rfp_id(
240
- self, proposal: CreateProposalRequest, rfp_id: str = Path(...)
241
- ):
242
- async with self.__proposal_service() as service:
243
- try:
244
- data = proposal.model_dump(exclude_unset=True, mode="json")
245
- data["rfp_id"] = rfp_id
246
- new_proposal = await service.create_proposal(proposal=data)
247
- return ResponseProposal(
248
- status="success", data=[Proposal(**new_proposal)]
249
- )
250
- except Exception as e:
251
- logger.exception(e)
252
- raise HTTPException(
253
- status_code=500, detail="Failed to create proposal."
254
- )
255
-
256
- async def update_proposal(
257
- self, proposal: ProposalUpdateRequest, proposal_id: str = Path(...)
258
- ):
259
- async with self.__proposal_service() as service:
260
- try:
261
- updated_proposal = await service.update_proposal(
262
- proposal_id=proposal_id,
263
- proposal=proposal.model_dump(exclude_unset=True),
264
- )
265
- return ResponseProposal(
266
- status="success", data=[Proposal(**updated_proposal)]
267
- )
268
- except Exception as e:
269
- logger.exception(e)
270
- raise HTTPException(
271
- status_code=500, detail="Failed to update proposal."
272
- )
273
-
274
- async def update_proposal_by_proposal_and_rfp_id(
275
- self,
276
- proposal: UpdateProposalRequest,
277
- rfp_id: str = Path(...),
278
- proposal_id: str = Path(...),
279
- ):
280
- async with self.__proposal_service() as service:
281
- try:
282
- data = proposal.model_dump(exclude_unset=True, mode="json")
283
- data["rfp_id"] = rfp_id
284
- updated_proposal = await service.update_proposal(
285
- proposal_id=proposal_id,
286
- proposal=proposal.model_dump(exclude_unset=True),
287
- )
288
- return ResponseProposal(
289
- status="success", data=[Proposal(**updated_proposal)]
290
- )
291
- except Exception as e:
292
- logger.exception(e)
293
- raise HTTPException(
294
- status_code=500, detail="Failed to update proposal."
295
- )
296
-
297
- async def delete_proposal(self, proposal_id: str = Path(...)):
298
- async with self.__proposal_service() as service:
299
- try:
300
- status = await service.delete_proposal(proposal_id=proposal_id)
301
- if not status:
302
- raise HTTPException(status_code=404, detail="Proposal not found.")
303
- return ProposalDeleteResponse(status="success")
304
- except Exception as e:
305
- logger.exception(e)
306
- raise HTTPException(
307
- status_code=500, detail="Failed to delete proposal."
308
- )
309
-
310
- async def delete_proposals_by_rfp_id(self, rfp_id: str = Path(...)):
311
- async with self.__proposal_service() as service:
312
- try:
313
- status = await service.delete_proposal(rfp_id=rfp_id)
314
- if not status:
315
- raise HTTPException(status_code=404, detail="Proposal not found.")
316
- return ProposalDeleteResponse(status="success")
317
- except Exception as e:
318
- logger.exception(e)
319
- raise HTTPException(
320
- status_code=500, detail="Failed to delete proposal."
321
- )
322
-
323
- async def delete_proposals_by_proposal_and_rfp_id(
324
- self, rfp_id: str = Path(...), proposal_id: str = Path(...)
325
- ):
326
- async with self.__proposal_service() as service:
327
- try:
328
- status = await service.delete_proposal(
329
- rfp_id=rfp_id, proposal_id=proposal_id
330
- )
331
- if not status:
332
- raise HTTPException(status_code=404, detail="Proposal not found.")
333
- return ProposalDeleteResponse(status="success")
334
- except Exception as e:
335
- logger.exception(e)
336
- raise HTTPException(
337
- status_code=500, detail="Failed to delete proposal."
338
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_proposal_detailed_analysis_controller.py DELETED
@@ -1,291 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path
2
- from pydantic import BaseModel
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
-
9
- from src.models import OperationCriteria, ProposalDetailAnalysis
10
-
11
- from src.services import ProposalDetailedAnalysisService
12
-
13
-
14
- class ProposalDetailedAnalysis(BaseModel):
15
- id: UUID
16
- proposal_id: UUID
17
- ai_assessment_result: str
18
- confidence: float
19
- operation_criteria: OperationCriteria
20
- created_at: datetime
21
- updated_at: datetime
22
-
23
-
24
- class ProposalDetailedAnalysisResponse(BaseModel):
25
- status: str
26
- data: Optional[List[ProposalDetailedAnalysis]]
27
-
28
-
29
- class OperationCriteriaResult(BaseModel):
30
- confidence: float
31
- ai_assessment_result: str
32
-
33
-
34
- class ProposalCompleteDetailedAnalysis(BaseModel):
35
- mission_alignment: Optional[OperationCriteriaResult] = None
36
- technical_feasibility: Optional[OperationCriteriaResult] = None
37
- cost_efficiency: Optional[OperationCriteriaResult] = None
38
- risk_assessment: Optional[OperationCriteriaResult] = None
39
-
40
-
41
- class CreateProposalDetailedAnalysisRequest(BaseModel):
42
- data: ProposalCompleteDetailedAnalysis
43
-
44
-
45
- class CompleteAnalysisResponse(BaseModel):
46
- status: str
47
- data: Optional[ProposalCompleteDetailedAnalysis]
48
-
49
-
50
- class DeleteResponse(BaseModel):
51
- status: str
52
-
53
-
54
- class CreateDetailedAnalysisRequest(BaseModel):
55
- proposal_id: UUID
56
- confidence: float
57
- ai_assessment_result: str
58
- operation_criteria: OperationCriteria
59
-
60
-
61
- class UpdateDetailedAnalysisRequest(BaseModel):
62
- proposal_id: Optional[UUID] = None
63
- confidence: Optional[float] = None
64
- ai_assessment_result: Optional[str] = None
65
- operation_criteria: Optional[OperationCriteria] = None
66
-
67
-
68
- class ProposalDetailedController:
69
- def __init__(self):
70
- self.service = ProposalDetailedAnalysisService
71
- self.router = APIRouter()
72
- self.router.add_api_route(
73
- "/proposal-detailed-analysis",
74
- self.get_proposal_analysis,
75
- methods=["GET"],
76
- tags=["Proposal Detailed Analysis"],
77
- response_model=ProposalDetailedAnalysisResponse,
78
- )
79
- self.router.add_api_route(
80
- "/proposal-detailed-analysis/{id}",
81
- self.get_proposal_analysis_by_id,
82
- methods=["GET"],
83
- tags=["Proposal Detailed Analysis by ID"],
84
- response_model=ProposalDetailedAnalysisResponse,
85
- )
86
- self.router.add_api_route(
87
- "/proposals/{proposal_id}/proposal-detailed-analysis",
88
- self.get_proposal_analysis_by_proposal_id,
89
- methods=["GET"],
90
- tags=["Proposal Detailed Analysis by Proposal ID"],
91
- response_model=CompleteAnalysisResponse,
92
- )
93
- self.router.add_api_route(
94
- "/proposal-detailed-analysis",
95
- self.create_proposal_analysis,
96
- methods=["POST"],
97
- tags=["Proposal Detailed Analysis"],
98
- response_model=ProposalDetailedAnalysisResponse,
99
- )
100
- self.router.add_api_route(
101
- "/proposals/{proposal_id}/proposal-detailed-analysis",
102
- self.create_complete_proposal_analysis,
103
- methods=["POST"],
104
- tags=["Proposal Detailed Analysis by Proposal ID"],
105
- response_model=CompleteAnalysisResponse,
106
- )
107
- self.router.add_api_route(
108
- "/proposal-detailed-analysis/{id}",
109
- self.update_proposal_analysis,
110
- methods=["PUT"],
111
- tags=["Proposal Detailed Analysis by ID"],
112
- response_model=ProposalDetailedAnalysisResponse,
113
- )
114
- self.router.add_api_route(
115
- "/proposals/{proposal_id}/proposal-detailed-analysis",
116
- self.update_complete_proposal_analysis,
117
- methods=["PUT"],
118
- tags=["Proposal Detailed Analysis by Proposal ID"],
119
- response_model=CompleteAnalysisResponse,
120
- )
121
- self.router.add_api_route(
122
- "/proposal-detailed-analysis/{id}",
123
- self.delete_proposal_analysis,
124
- methods=["DELETE"],
125
- tags=["Proposal Detailed Analysis by ID"],
126
- response_model=DeleteResponse,
127
- )
128
- self.router.add_api_route(
129
- "/proposals/{proposal_id}/proposal-detailed-analysis",
130
- self.delete_proposal_analysis_by_proposal_id,
131
- methods=["DELETE"],
132
- tags=["Proposal Detailed Analysis by Proposal ID"],
133
- response_model=DeleteResponse,
134
- )
135
- self.router.add_api_route(
136
- "/proposals/{proposal_id}/proposal-detailed-analysis/{id}",
137
- self.delete_proposal_analysis_by_proposal_and_id,
138
- methods=["DELETE"],
139
- tags=["Proposal Detailed Analysis by Proposal and ID"],
140
- response_model=DeleteResponse,
141
- )
142
-
143
- async def get_proposal_analysis(self):
144
- async with self.service() as service:
145
- try:
146
- result = await service.get_proposal_analysis()
147
- if not result:
148
- return ProposalDetailedAnalysisResponse(status="success", data=[])
149
- return ProposalDetailedAnalysisResponse(
150
- status="success",
151
- data=[ProposalDetailedAnalysis(**r) for r in result],
152
- )
153
- except Exception as e:
154
- logger.exception(e)
155
- raise HTTPException(
156
- status_code=500, detail="Failed to get proposal analysis."
157
- )
158
-
159
- async def get_proposal_analysis_by_id(self, id: str = Path(...)):
160
- async with self.service() as service:
161
- try:
162
- result = await service.get_proposal_analysis(id=id)
163
- return ProposalDetailedAnalysisResponse(
164
- status="success",
165
- data=[ProposalDetailedAnalysis(**r) for r in result],
166
- )
167
- except Exception as e:
168
- logger.exception(e)
169
- raise HTTPException(
170
- status_code=500, detail="Failed to get proposal analysis."
171
- )
172
-
173
- async def get_proposal_analysis_by_proposal_id(self, proposal_id: str = Path(...)):
174
- async with self.service() as service:
175
- try:
176
- result = await service.get_proposal_analysis_by_proposal_id(
177
- proposal_id=proposal_id
178
- )
179
- return CompleteAnalysisResponse(status="success", data=result)
180
- except Exception as e:
181
- logger.exception(e)
182
- raise HTTPException(
183
- status_code=500, detail="Failed to get proposal analysis."
184
- )
185
-
186
- async def create_proposal_analysis(self, analysis: CreateDetailedAnalysisRequest):
187
- async with self.service() as service:
188
- try:
189
- result = await service.create_proposal_analysis(
190
- data=analysis.model_dump(mode="json", exclude_unset=True)
191
- )
192
- return ProposalDetailedAnalysisResponse(
193
- status="success",
194
- data=[ProposalDetailedAnalysis(**r) for r in result],
195
- )
196
- except Exception as e:
197
- logger.exception(e)
198
- raise HTTPException(
199
- status_code=500, detail="Failed to create proposal analysis."
200
- )
201
-
202
- async def create_complete_proposal_analysis(
203
- self,
204
- analysis: CreateProposalDetailedAnalysisRequest,
205
- proposal_id: UUID = Path(...),
206
- ):
207
- async with self.service() as service:
208
- try:
209
- data = analysis.model_dump(mode="json", exclude_unset=True)
210
- data["proposal_id"] = proposal_id
211
- result = await service.create_complete_proposal_analysis(data=data)
212
- return CompleteAnalysisResponse(status="success", data=result)
213
- except Exception as e:
214
- logger.exception(e)
215
- raise HTTPException(
216
- status_code=500, detail="Failed to create proposal analysis."
217
- )
218
-
219
- async def update_proposal_analysis(
220
- self, analysis: UpdateDetailedAnalysisRequest, id: UUID = Path(...)
221
- ):
222
- async with self.service() as service:
223
- try:
224
- data = analysis.model_dump(mode="json", exclude_unset=True)
225
- data["id"] = id
226
- result = await service.update_proposal_analysis(data=data)
227
- return ProposalDetailedAnalysisResponse(
228
- status="success",
229
- data=[ProposalDetailedAnalysis(**r) for r in result],
230
- )
231
- except Exception as e:
232
- logger.exception(e)
233
- raise HTTPException(
234
- status_code=500, detail="Failed to update proposal analysis."
235
- )
236
-
237
- async def update_complete_proposal_analysis(
238
- self,
239
- analysis: CreateProposalDetailedAnalysisRequest,
240
- proposal_id: UUID = Path(...),
241
- ):
242
- async with self.service() as service:
243
- try:
244
- data = analysis.model_dump(mode="json", exclude_unset=True)
245
- data["proposal_id"] = proposal_id
246
- result = await service.update_complete_proposal_analysis(data=data)
247
- return CompleteAnalysisResponse(status="success", data=result)
248
- except Exception as e:
249
- logger.exception(e)
250
- raise HTTPException(
251
- status_code=500, detail="Failed to update proposal analysis."
252
- )
253
-
254
- async def delete_proposal_analysis(self, id: str = Path(...)):
255
- async with self.service() as service:
256
- try:
257
- result = await service.delete_proposal_analysis(id=id)
258
- return DeleteResponse(status="success")
259
- except Exception as e:
260
- logger.exception(e)
261
- raise HTTPException(
262
- status_code=500, detail="Failed to delete proposal analysis."
263
- )
264
-
265
- async def delete_proposal_analysis_by_proposal_id(
266
- self, proposal_id: str = Path(...)
267
- ):
268
- async with self.service() as service:
269
- try:
270
- result = await service.delete_proposal_analysis(proposal_id=proposal_id)
271
- return DeleteResponse(status="success")
272
- except Exception as e:
273
- logger.exception(e)
274
- raise HTTPException(
275
- status_code=500, detail="Failed to delete proposal analysis."
276
- )
277
-
278
- async def delete_proposal_analysis_by_proposal_and_id(
279
- self, proposal_id: str = Path(...), id: str = Path(...)
280
- ):
281
- async with self.service() as service:
282
- try:
283
- result = await service.delete_proposal_analysis(
284
- proposal_id=proposal_id, id=id
285
- )
286
- return DeleteResponse(status="success")
287
- except Exception as e:
288
- logger.exception(e)
289
- raise HTTPException(
290
- status_code=500, detail="Failed to delete proposal analysis."
291
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_proposal_evaluation_controller.py DELETED
@@ -1,209 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Query, Path
2
- from pydantic import BaseModel, field_validator
3
- from typing import List, Optional
4
- from uuid import UUID
5
- from datetime import datetime
6
-
7
- from src.config import logger
8
- from src.models import EvaluationType
9
- from src.services import ProposalEvaluationService
10
-
11
-
12
- class Evaluation(BaseModel):
13
- id: UUID
14
- proposal_id: UUID
15
- evaluation_type: EvaluationType
16
- ai_score: Optional[float] = None
17
- adjusted_score: Optional[float] = None
18
- created_at: datetime
19
- updated_at: datetime
20
-
21
- @field_validator("ai_score", "adjusted_score")
22
- @classmethod
23
- def round_to_two_decimals(cls, v):
24
- if v is not None:
25
- return round(v, 2)
26
- return v
27
-
28
-
29
- class Response(BaseModel):
30
- status: str
31
- data: Optional[List[Evaluation]] = None
32
-
33
-
34
- class RequestEvaluation(BaseModel):
35
- evaluation_type: Optional[EvaluationType] = None
36
- ai_score: Optional[float] = None
37
- adjusted_score: Optional[float] = None
38
-
39
-
40
- class DeleteResponse(BaseModel):
41
- status: str
42
-
43
-
44
- class CreateEvaluation(BaseModel):
45
- evaluation_type: EvaluationType
46
- ai_score: Optional[float] = None
47
- adjusted_score: Optional[float] = None
48
-
49
-
50
- class CreateEvaluationRequest(BaseModel):
51
- data: List[CreateEvaluation]
52
-
53
-
54
- class UpdateEvaluation(BaseModel):
55
- evaluation_type: Optional[EvaluationType] = None
56
- ai_score: Optional[float] = None
57
- adjusted_score: Optional[float] = None
58
-
59
-
60
- class ProposalEvaluationController:
61
- def __init__(self):
62
- self.service = ProposalEvaluationService
63
- self.router = APIRouter()
64
- self.router.add_api_route(
65
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations",
66
- self.get_evaluation_by_proposal_and_rfp_id,
67
- methods=["GET"],
68
- response_model=Response,
69
- tags=["Evaluations by Proposal and RFP ID"],
70
- )
71
- self.router.add_api_route(
72
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{id}",
73
- self.get_evaluation_by_proposal_and_rfp_id_and_id,
74
- methods=["GET"],
75
- response_model=Response,
76
- tags=["Evaluations by Proposal and RFP ID and ID"],
77
- )
78
-
79
- self.router.add_api_route(
80
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations",
81
- self.create_evaluation_by_proposal_and_rfp_id,
82
- methods=["POST"],
83
- response_model=Response,
84
- tags=["Evaluations by Proposal and RFP ID"],
85
- )
86
-
87
- self.router.add_api_route(
88
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{id}",
89
- self.update_evaluation_by_proposal_and_rfp_id_and_id,
90
- methods=["PUT"],
91
- response_model=Response,
92
- tags=["Evaluations by Proposal and RFP ID and ID"],
93
- )
94
-
95
- self.router.add_api_route(
96
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations",
97
- self.delete_evaluation_by_proposal_and_rfp_id,
98
- methods=["DELETE"],
99
- response_model=DeleteResponse,
100
- tags=["Evaluations by Proposal and RFP ID"],
101
- )
102
- self.router.add_api_route(
103
- "/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{id}",
104
- self.delete_evaluation_by_proposal_and_rfp_id_and_id,
105
- methods=["DELETE"],
106
- response_model=DeleteResponse,
107
- tags=["Evaluations by Proposal and RFP ID and ID"],
108
- )
109
-
110
- async def get_evaluation_by_proposal_and_rfp_id(
111
- self,
112
- rfp_id: str = Path(...),
113
- proposal_id: str = Path(...),
114
- evaluation_type: Optional[EvaluationType] = Query(None),
115
- ):
116
- try:
117
- async with self.service() as service:
118
- results = await service.get_evaluations(
119
- proposal_id=proposal_id, evaluation_type=evaluation_type
120
- )
121
- return Response(
122
- status="success",
123
- data=[Evaluation(**result) for result in results],
124
- )
125
- except Exception as e:
126
- logger.exception(e)
127
- raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
128
-
129
- async def get_evaluation_by_proposal_and_rfp_id_and_id(
130
- self, rfp_id: str = Path(...), proposal_id: str = Path(...), id: str = Path(...)
131
- ):
132
- try:
133
- async with self.service() as service:
134
- result = await service.get_evaluations(proposal_id=proposal_id, id=id)
135
- return Response(
136
- status="success",
137
- data=[Evaluation(**result) for result in result],
138
- )
139
- except Exception as e:
140
- logger.exception(e)
141
- raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
142
-
143
- async def create_evaluation_by_proposal_and_rfp_id(
144
- self,
145
- evaluation: CreateEvaluationRequest,
146
- rfp_id: str = Path(...),
147
- proposal_id: str = Path(...),
148
- ):
149
- try:
150
- async with self.service() as service:
151
- result = await service.create_evaluations(
152
- proposal_id=proposal_id,
153
- evaluations=evaluation.model_dump(mode="json", exclude_unset=True)[
154
- "data"
155
- ],
156
- )
157
- return Response(
158
- status="success",
159
- data=[Evaluation(**result) for result in result],
160
- )
161
- except Exception as e:
162
- logger.exception(e)
163
- raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
164
-
165
- async def update_evaluation_by_proposal_and_rfp_id_and_id(
166
- self,
167
- evaluation: UpdateEvaluation,
168
- rfp_id: str = Path(...),
169
- proposal_id: str = Path(...),
170
- id: str = Path(...),
171
- ):
172
- try:
173
- async with self.service() as service:
174
- result = await service.update_evaluations(
175
- proposal_id=proposal_id,
176
- id=id,
177
- evaluations=evaluation.model_dump(mode="json", exclude_unset=True),
178
- )
179
- return Response(
180
- status="success",
181
- data=[Evaluation(**result) for result in result],
182
- )
183
- except Exception as e:
184
- logger.exception(e)
185
- raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
186
-
187
- async def delete_evaluation_by_proposal_and_rfp_id(
188
- self, rfp_id: str = Path(...), proposal_id: str = Path(...)
189
- ):
190
- try:
191
- async with self.service() as service:
192
- result = await service.delete_evaluations(proposal_id=proposal_id)
193
- return DeleteResponse(status="success")
194
- except Exception as e:
195
- logger.exception(e)
196
- raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
197
-
198
- async def delete_evaluation_by_proposal_and_rfp_id_and_id(
199
- self, rfp_id: str = Path(...), proposal_id: str = Path(...), id: str = Path(...)
200
- ):
201
- try:
202
- async with self.service() as service:
203
- result = await service.delete_evaluations(
204
- proposal_id=proposal_id, id=id
205
- )
206
- return DeleteResponse(status="success")
207
- except Exception as e:
208
- logger.exception(e)
209
- raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/_rfp_controller.py DELETED
@@ -1,170 +0,0 @@
1
- from fastapi import Path
2
- from fastapi import APIRouter, HTTPException, Query
3
- from pydantic import BaseModel, Field, field_validator
4
- from typing import List, Optional
5
- from uuid import UUID
6
-
7
- from src.config import logger
8
- from src.services import RFPService
9
- from datetime import datetime
10
-
11
- from src.models import RFPStatus
12
-
13
-
14
- class RFP(BaseModel):
15
- id: UUID
16
- rfp_number: str
17
- name: str
18
- received_proposals: int
19
- evaluated_count: int
20
- awaiting_evaluation: int
21
- target_award_date: Optional[datetime] = None
22
- ko_name: Optional[str] = None
23
- award_type: Optional[str] = None
24
- google_drive_id: Optional[str] = None
25
- status: RFPStatus
26
- comparative_weights_locked: Optional[bool] = False
27
- created_at: datetime
28
- updated_at: datetime
29
-
30
-
31
- class ResponseRFP(BaseModel):
32
- status: str
33
- data: Optional[List[RFP]]
34
-
35
-
36
- class RFPRequest(BaseModel):
37
- rfp_number: str
38
- name: str
39
- received_proposals: int
40
- evaluated_count: int
41
- target_award_date: Optional[datetime] = None
42
- ko_name: Optional[str] = None
43
- award_type: Optional[str] = None
44
- google_drive_id: Optional[str] = None
45
- status: RFPStatus
46
- comparative_weights_locked: Optional[bool] = False
47
- awaiting_evaluation: int
48
-
49
-
50
- class RFPUpdateRequest(BaseModel):
51
- rfp_number: Optional[str] = None
52
- name: Optional[str] = None
53
- received_proposals: Optional[int] = None
54
- evaluated_count: Optional[int] = None
55
- target_award_date: Optional[datetime] = None
56
- ko_name: Optional[str] = None
57
- award_type: Optional[str] = None
58
- google_drive_id: Optional[str] = None
59
- status: Optional[RFPStatus] = None
60
- comparative_weights_locked: Optional[bool] = None
61
- awaiting_evaluation: Optional[int] = None
62
-
63
-
64
- class RFPDeleteResponse(BaseModel):
65
- status: str
66
-
67
-
68
- class RFPController:
69
- def __init__(self):
70
- self.__rfp_service = RFPService
71
- self.router = APIRouter()
72
- self.router.add_api_route(
73
- "/rfps",
74
- self.get_rfps,
75
- methods=["GET"],
76
- response_model=ResponseRFP,
77
- tags=["RFPs"],
78
- )
79
- self.router.add_api_route(
80
- "/rfps/{rfp_id}",
81
- self.get_rfps_by_id,
82
- methods=["GET"],
83
- response_model=ResponseRFP,
84
- tags=["RFPs by ID"],
85
- )
86
- self.router.add_api_route(
87
- "/rfps",
88
- self.create_rfp,
89
- methods=["POST"],
90
- response_model=ResponseRFP,
91
- tags=["RFPs"],
92
- )
93
- self.router.add_api_route(
94
- "/rfps/{rfp_id}",
95
- self.update_rfp,
96
- methods=["PUT"],
97
- response_model=ResponseRFP,
98
- tags=["RFPs by ID"],
99
- )
100
- self.router.add_api_route(
101
- "/rfps/{rfp_id}",
102
- self.delete_rfp,
103
- methods=["DELETE"],
104
- response_model=RFPDeleteResponse,
105
- tags=["RFPs by ID"],
106
- )
107
-
108
- async def get_rfps(self, status: Optional[RFPStatus] = Query(None)):
109
- async with self.__rfp_service() as service:
110
- try:
111
- rfps = await service.get_rfps(status=status)
112
- return ResponseRFP(status="success", data=[RFP(**rfp) for rfp in rfps])
113
- except HTTPException as e:
114
- logger.warning(e)
115
- raise e
116
- except Exception as e:
117
- logger.exception(e)
118
- raise HTTPException(status_code=500, detail="Failed to retrieve RFPs.")
119
-
120
- async def get_rfps_by_id(self, rfp_id: str = Path(...)):
121
- async with self.__rfp_service() as service:
122
- try:
123
- rfps = await service.get_rfps(rfp_id=rfp_id)
124
- return ResponseRFP(status="success", data=[RFP(**rfp) for rfp in rfps])
125
- except HTTPException as e:
126
- logger.warning(e)
127
- raise e
128
- except Exception as e:
129
- logger.exception(e)
130
- raise HTTPException(status_code=500, detail="Failed to retrieve RFPs.")
131
-
132
- async def create_rfp(self, rfp: RFPRequest):
133
- async with self.__rfp_service() as service:
134
- try:
135
- rfp = await service.create_rfp(rfp.model_dump(exclude_unset=True))
136
- return ResponseRFP(status="success", data=[RFP(**rfp)])
137
- except HTTPException as e:
138
- logger.warning(e)
139
- raise e
140
- except Exception as e:
141
- logger.exception(e)
142
- raise HTTPException(status_code=500, detail="Failed to create RFP.")
143
-
144
- async def update_rfp(self, rfp: RFPUpdateRequest, rfp_id: str = Path(...)):
145
- async with self.__rfp_service() as service:
146
- try:
147
- rfp = await service.update_rfp(
148
- rfp_id=rfp_id, rfp=rfp.model_dump(exclude_unset=True)
149
- )
150
- return ResponseRFP(status="success", data=[RFP(**rfp)])
151
- except HTTPException as e:
152
- logger.warning(e)
153
- raise e
154
- except Exception as e:
155
- logger.exception(e)
156
- raise HTTPException(status_code=500, detail="Failed to update RFP.")
157
-
158
- async def delete_rfp(self, rfp_id: str = Path(...)):
159
- async with self.__rfp_service() as service:
160
- try:
161
- status = await service.delete_rfp(rfp_id)
162
- if not status:
163
- raise HTTPException(status_code=404, detail="RFP not found.")
164
- return RFPDeleteResponse(status="success")
165
- except HTTPException as e:
166
- logger.warning(e)
167
- raise e
168
- except Exception as e:
169
- logger.exception(e)
170
- raise HTTPException(status_code=500, detail="Failed to delete RFP.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/__init__.py DELETED
@@ -1,50 +0,0 @@
1
- from ._base import Base
2
- from ._proposal import Proposal, ProposalStatus, GateCriteria
3
- from ._rfp import RFP, RFPStatus
4
- from ._proposal_ai_analysis import (
5
- ProposalAIAnalysis,
6
- ProposalStrength,
7
- ProposalWeakness,
8
- ProposalImprovement,
9
- ProposalRejectionReason,
10
- Category
11
- )
12
- from ._proposal_detailed_analysis import ProposalDetailAnalysis, OperationCriteria
13
- from ._proposal_query_models import QueryType, CreateOrUpdateType
14
- from ._letters import Letter, LetterType
15
- from ._evaluation import EvaluationCriteria, EvaluationCriteriaType
16
- from ._comparitive_weights import ComparativeWeights, Weights
17
- from ._proposal_evaluation import Evaluations, EvaluationType
18
- from ._analysis import Analysis, AnalysisType, AnalysisStatus
19
-
20
- __all__ = [
21
- "Base",
22
- "Proposal",
23
- "RFP",
24
- "ProposalAIAnalysis",
25
- "ProposalStrength",
26
- "ProposalWeakness",
27
- "ProposalImprovement",
28
- "ProposalRejectionReason",
29
- "ProposalDetailAnalysis",
30
- "ProposalStatus",
31
- "QueryType",
32
- "CreateOrUpdateType",
33
- "OperationCriteria",
34
- "GateCriteria",
35
- "Letter",
36
- "LetterType",
37
- "EvaluationCriteria",
38
- "EvaluationCriteriaType",
39
- "RFPStatus",
40
- "Category",
41
- "ComparativeWeights",
42
- "Weights",
43
- "Evaluations",
44
- "EvaluationType",
45
- "Analysis",
46
- "AnalysisType",
47
- "AnalysisStatus",
48
- ]
49
- __version__ = "0.1.0"
50
- __author__ = "Aryan Jain"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_analysis.py DELETED
@@ -1,42 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Column,
5
- DateTime,
6
- Enum,
7
- Float,
8
- ForeignKey,
9
- Integer,
10
- String,
11
- func,
12
- )
13
- from sqlalchemy.dialects.postgresql import UUID
14
- from pydantic import BaseModel
15
- from ._base import Base
16
-
17
-
18
- class AnalysisType(PyEnum):
19
- STRENGTHS = "STRENGTHS"
20
- WEAKNESSES = "WEAKNESSES"
21
- DEFICIENCIES = "DEFICIENCIES"
22
-
23
- class AnalysisStatus(PyEnum):
24
- ACCEPTED = "ACCEPTED"
25
- REJECTED = "REJECTED"
26
- PENDING = "PENDING"
27
-
28
- class Analysis(Base):
29
- __tablename__ = "analysis"
30
- id = Column(UUID(as_uuid=True), primary_key=True)
31
- evaluation_id = Column(
32
- UUID(as_uuid=True),
33
- ForeignKey("evaluations.id", ondelete="CASCADE"),
34
- nullable=False,
35
- )
36
- insights = Column(String, nullable=False)
37
- analysis_type = Column(Enum(AnalysisType), nullable=False)
38
- status = Column(Enum(AnalysisStatus, name="analysisstatus"), nullable=False, default=AnalysisStatus.PENDING)
39
- created_at = Column(DateTime(), nullable=False, default=func.now())
40
- updated_at = Column(
41
- DateTime(), nullable=False, default=func.now(), onupdate=func.now()
42
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_base.py DELETED
@@ -1,3 +0,0 @@
1
- from sqlalchemy.orm import declarative_base
2
-
3
- Base = declarative_base()
 
 
 
 
src/models/_comparitive_weights.py DELETED
@@ -1,47 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Boolean,
5
- Column,
6
- DateTime,
7
- Enum,
8
- Float,
9
- ForeignKey,
10
- Integer,
11
- String,
12
- func,
13
- )
14
- from sqlalchemy.dialects.postgresql import UUID
15
- from pydantic import BaseModel
16
- from ._base import Base
17
-
18
-
19
- class Weights(PyEnum):
20
- NONE = "NONE"
21
- LOW = "LOW"
22
- MEDIUM = "MEDIUM"
23
- HIGH = "HIGH"
24
-
25
-
26
- class ComparativeWeights(Base):
27
- __tablename__ = "comparative_weights"
28
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
29
- rfp_id = Column(
30
- UUID(as_uuid=True),
31
- ForeignKey("rfps.id", ondelete="CASCADE"),
32
- nullable=False,
33
- unique=True,
34
- )
35
- technical_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False)
36
- management_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False)
37
- past_performance_weight = Column(
38
- Enum(Weights, name="comparativeweights"), nullable=False
39
- )
40
- price_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False)
41
- strengths_weight = Column(Float, nullable=False, default=15)
42
- weaknesses_weight = Column(Float, nullable=False, default=5)
43
- flag = Column(Boolean(), nullable=True, default=False)
44
- created_at = Column(DateTime, nullable=False, default=func.now())
45
- updated_at = Column(
46
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
47
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_evaluation.py DELETED
@@ -1,34 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Column,
5
- DateTime,
6
- Enum,
7
- Float,
8
- ForeignKey,
9
- Integer,
10
- String,
11
- func,
12
- )
13
- from sqlalchemy.dialects.postgresql import UUID
14
- from pydantic import BaseModel
15
- from ._base import Base
16
-
17
-
18
- class EvaluationCriteriaType(PyEnum):
19
- EVALUATION = "EVALUATION"
20
- GATE = "GATE"
21
-
22
-
23
- class EvaluationCriteria(Base):
24
- __tablename__ = "evaluation_criteria_details"
25
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
26
- rfp_id = Column(
27
- UUID(as_uuid=True), ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False
28
- )
29
- evaluation_criteria = Column(String, nullable=True)
30
- evaluation_criteria_type = Column(Enum(EvaluationCriteriaType), nullable=False)
31
- created_at = Column(DateTime, nullable=False, default=func.now())
32
- updated_at = Column(
33
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
34
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_letters.py DELETED
@@ -1,36 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Column,
5
- DateTime,
6
- Enum,
7
- Float,
8
- ForeignKey,
9
- Integer,
10
- String,
11
- func,
12
- )
13
- from sqlalchemy.dialects.postgresql import UUID
14
- from pydantic import BaseModel
15
- from ._base import Base
16
-
17
-
18
- class LetterType(PyEnum):
19
- UO = "UO"
20
- DEBRIEF = "DEBRIEF"
21
-
22
-
23
- class Letter(Base):
24
- __tablename__ = "letters"
25
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
26
- proposal_id = Column(
27
- UUID(as_uuid=True),
28
- ForeignKey("proposals.id", ondelete="CASCADE"),
29
- nullable=False,
30
- )
31
- letter = Column(String, nullable=True)
32
- letter_type = Column(Enum(LetterType), nullable=False)
33
- created_at = Column(DateTime, nullable=False, default=func.now())
34
- updated_at = Column(
35
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
36
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_proposal.py DELETED
@@ -1,46 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Column,
5
- DateTime,
6
- Enum,
7
- Float,
8
- ForeignKey,
9
- Integer,
10
- String,
11
- func,
12
- )
13
- from sqlalchemy.dialects.postgresql import UUID
14
- from pydantic import BaseModel
15
- from ._base import Base
16
-
17
-
18
- class ProposalStatus(PyEnum):
19
- APPROVED = "APPROVED"
20
- EVALUATED = "EVALUATED"
21
- IN_REVIEW = "IN_REVIEW"
22
-
23
-
24
- class GateCriteria(PyEnum):
25
- PASS = "PASS"
26
- FAIL = "FAIL"
27
- IN_REVIEW = "IN_REVIEW"
28
-
29
-
30
- class Proposal(Base):
31
- __tablename__ = "proposals"
32
-
33
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
34
- rfp_id = Column(
35
- UUID(as_uuid=True), ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False
36
- )
37
- name = Column(String, nullable=False)
38
- tep = Column(String, nullable=False)
39
- gate_criteria = Column(Enum(GateCriteria), nullable=False)
40
- status = Column(Enum(ProposalStatus), nullable=False)
41
- ai_score = Column(Float, nullable=True)
42
- final_score = Column(Float, nullable=True)
43
- created_at = Column(DateTime, nullable=False, default=func.now())
44
- updated_at = Column(
45
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
46
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_proposal_ai_analysis.py DELETED
@@ -1,103 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Column,
5
- DateTime,
6
- Enum,
7
- Float,
8
- ForeignKey,
9
- Integer,
10
- String,
11
- func,
12
- )
13
- from sqlalchemy.dialects.postgresql import UUID
14
- from ._base import Base
15
-
16
- class Category(PyEnum):
17
- TECHNICAL = "TECHNICAL"
18
- MANAGEMENT = "MANAGEMENT"
19
- PAST_PERFORMANCE = "PAST_PERFORMANCE"
20
- PRICE = "PRICE"
21
-
22
- class ProposalAIAnalysis(Base):
23
- __tablename__ = "proposal_ai_analyses"
24
-
25
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
26
- proposal_id = Column(
27
- UUID(as_uuid=True),
28
- ForeignKey("proposals.id", ondelete="CASCADE"),
29
- nullable=False,
30
- )
31
- ai_score = Column(Float, nullable=False)
32
- created_at = Column(DateTime, nullable=False, default=func.now())
33
- updated_at = Column(
34
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
35
- )
36
-
37
-
38
- class ProposalStrength(Base):
39
- __tablename__ = "proposal_strengths"
40
-
41
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
42
- proposal_id = Column(
43
- UUID(as_uuid=True),
44
- ForeignKey("proposals.id", ondelete="CASCADE"),
45
- nullable=False,
46
- )
47
- strength_point = Column(String, nullable=False)
48
- category = Column(Enum(Category, name="strengthcategory"), nullable=False)
49
- created_at = Column(DateTime, nullable=False, default=func.now())
50
- updated_at = Column(
51
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
52
- )
53
-
54
-
55
- class ProposalRejectionReason(Base):
56
- __tablename__ = "proposal_rejection_reasons"
57
-
58
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
59
- proposal_id = Column(
60
- UUID(as_uuid=True),
61
- ForeignKey("proposals.id", ondelete="CASCADE"),
62
- nullable=False,
63
- )
64
- reason = Column(String, nullable=False)
65
- category = Column(Enum(Category, name="rejectioncategory"), nullable=False)
66
- created_at = Column(DateTime, nullable=False, default=func.now())
67
- updated_at = Column(
68
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
69
- )
70
-
71
-
72
- class ProposalWeakness(Base):
73
- __tablename__ = "proposal_weaknesses"
74
-
75
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
76
- proposal_id = Column(
77
- UUID(as_uuid=True),
78
- ForeignKey("proposals.id", ondelete="CASCADE"),
79
- nullable=False,
80
- )
81
- weakness_point = Column(String, nullable=False)
82
- category = Column(Enum(Category, name="weaknesscategory"), nullable=False)
83
- created_at = Column(DateTime, nullable=False, default=func.now())
84
- updated_at = Column(
85
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
86
- )
87
-
88
-
89
- class ProposalImprovement(Base):
90
- __tablename__ = "proposal_improvements"
91
-
92
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
93
- proposal_id = Column(
94
- UUID(as_uuid=True),
95
- ForeignKey("proposals.id", ondelete="CASCADE"),
96
- nullable=False,
97
- )
98
- improvement_point = Column(String, nullable=False)
99
- category = Column(Enum(Category, name="improvementcategory"), nullable=False)
100
- created_at = Column(DateTime, nullable=False, default=func.now())
101
- updated_at = Column(
102
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
103
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_proposal_detailed_analysis.py DELETED
@@ -1,39 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Column,
5
- DateTime,
6
- Enum,
7
- Float,
8
- ForeignKey,
9
- Integer,
10
- String,
11
- func,
12
- )
13
- from sqlalchemy.dialects.postgresql import UUID
14
- from pydantic import BaseModel
15
- from ._base import Base
16
-
17
- class OperationCriteria(PyEnum):
18
- MISSION_ALIGNMENT = "MISSION_ALIGNMENT"
19
- TECHNICAL_FEASIBILITY = "TECHNICAL_FEASIBILITY"
20
- COST_EFFICIENCY = "COST_EFFICIENCY"
21
- RISK_ASSESSMENT = "RISK_ASSESSMENT"
22
-
23
-
24
- class ProposalDetailAnalysis(Base):
25
- __tablename__ = "proposal_detail_analyses"
26
-
27
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
28
- proposal_id = Column(
29
- UUID(as_uuid=True),
30
- ForeignKey("proposals.id", ondelete="CASCADE"),
31
- nullable=False,
32
- )
33
- ai_assessment_result = Column(String, nullable=False)
34
- confidence = Column(Float, nullable=False)
35
- operation_criteria = Column(Enum(OperationCriteria), nullable=False)
36
- created_at = Column(DateTime, nullable=False, default=func.now())
37
- updated_at = Column(
38
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
39
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_proposal_evaluation.py DELETED
@@ -1,40 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Column,
5
- DateTime,
6
- Enum,
7
- Float,
8
- ForeignKey,
9
- Integer,
10
- String,
11
- func,
12
- )
13
- from sqlalchemy.dialects.postgresql import UUID
14
- from pydantic import BaseModel
15
- from ._base import Base
16
-
17
-
18
- class EvaluationType(PyEnum):
19
- TECHNICAL = "TECHNICAL"
20
- MANAGEMENT = "MANAGEMENT"
21
- PAST_PERFORMANCE = "PAST_PERFORMANCE"
22
- PRICE = "PRICE"
23
-
24
-
25
- class Evaluations(Base):
26
- __tablename__ = "evaluations"
27
-
28
- id = Column(UUID(as_uuid=True), primary_key=True)
29
- proposal_id = Column(
30
- UUID(as_uuid=True),
31
- ForeignKey("proposals.id", ondelete="CASCADE"),
32
- nullable=False,
33
- )
34
- evaluation_type = Column(Enum(EvaluationType), nullable=False)
35
- ai_score = Column(Float(), nullable=True)
36
- adjusted_score = Column(Float(), nullable=True)
37
- created_at = Column(DateTime(), nullable=False, default=func.now())
38
- updated_at = Column(
39
- DateTime(), nullable=False, default=func.now(), onupdate=func.now()
40
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_proposal_query_models.py DELETED
@@ -1,13 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- class QueryType(PyEnum):
4
- ALL = "ALL"
5
- AI_SCORE = "AI_SCORE"
6
- STRENGTHS = "STRENGTHS"
7
- WEAKNESSES = "WEAKNESSES"
8
- REJECTION_REASONS = "REJECTION_REASONS"
9
- IMPROVEMENTS = "IMPROVEMENTS"
10
-
11
- class CreateOrUpdateType(PyEnum):
12
- ALL = "ALL"
13
- ID = "ID"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/_rfp.py DELETED
@@ -1,47 +0,0 @@
1
- from enum import Enum as PyEnum
2
-
3
- from sqlalchemy import (
4
- Boolean,
5
- Column,
6
- DateTime,
7
- Enum,
8
- Float,
9
- ForeignKey,
10
- Integer,
11
- String,
12
- func,
13
- )
14
- from sqlalchemy.dialects.postgresql import UUID
15
- from pydantic import BaseModel
16
- from ._base import Base
17
-
18
-
19
- class RFPStatus(PyEnum):
20
- ACTIVE = "ACTIVE"
21
- IN_PROGRESS = "IN_PROGRESS"
22
- NOT_YET_STARTED = "NOT_YET_STARTED"
23
- CUSTOMER_HOLD = "CUSTOMER_HOLD"
24
- UNSOLVED_ISSUES = "UNSOLVED_ISSUES"
25
- AWARDED = "AWARDED"
26
- PROTESTED = "PROTESTED"
27
-
28
-
29
- class RFP(Base):
30
- __tablename__ = "rfps"
31
-
32
- id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
33
- rfp_number = Column(String, nullable=False)
34
- name = Column(String, nullable=False)
35
- received_proposals = Column(Integer, nullable=False)
36
- evaluated_count = Column(Integer, nullable=False)
37
- awaiting_evaluation = Column(Integer, nullable=False)
38
- target_award_date = Column(DateTime, nullable=True)
39
- ko_name = Column(String, nullable=True)
40
- award_type = Column(String, nullable=True)
41
- google_drive_id = Column(String, nullable=True)
42
- status = Column(Enum(RFPStatus), nullable=False)
43
- comparative_weights_locked = Column(Boolean(), nullable=True, default=False)
44
- created_at = Column(DateTime, nullable=False, default=func.now())
45
- updated_at = Column(
46
- DateTime, nullable=False, default=func.now(), onupdate=func.now()
47
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/repositories/__init__.py DELETED
@@ -1,23 +0,0 @@
1
- from ._base_repository import BaseRepository
2
- from ._rfp_repository import RFPRepository
3
- from ._proposal_repository import ProposalRepository
4
- from ._proposal_detailed_analysis_repository import ProposalDetailedAnalysisRepository
5
- from ._letter_repository import LetterRepository
6
- from ._evaluation_repository import EvaluationRepository
7
- from ._comparative_weight_repository import ComparativeWeightRepository
8
- from ._proposal_evaluation_repository import ProposalEvaluationRepository
9
- from ._analysis_repository import AnalysisRepository
10
-
11
- __all__ = [
12
- "BaseRepository",
13
- "RFPRepository",
14
- "ProposalRepository",
15
- "ProposalDetailedAnalysisRepository",
16
- "LetterRepository",
17
- "EvaluationRepository",
18
- "ComparativeWeightRepository",
19
- "ProposalEvaluationRepository",
20
- "AnalysisRepository",
21
- ]
22
- __version__ = "0.1.0"
23
- __author__ = "Aryan Jain"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/repositories/_analysis_repository.py DELETED
@@ -1,99 +0,0 @@
1
- import uuid
2
- from sqlalchemy import select
3
- from ._base_repository import BaseRepository
4
- from src.models import AnalysisType, Analysis
5
- from src.utils import ScoreClient
6
-
7
-
8
- class AnalysisRepository(BaseRepository):
9
- def __init__(self):
10
- super().__init__(Analysis)
11
- self.score_client = ScoreClient
12
-
13
- async def __aenter__(self):
14
- return self
15
-
16
- async def __aexit__(self, exc_type, exc_value, traceback):
17
- pass
18
-
19
- async def get_analysis(
20
- self,
21
- evaluation_id: str = None,
22
- analysis_type: AnalysisType = None,
23
- id: str = None,
24
- ):
25
- async with self.get_session() as session:
26
- query = select(Analysis)
27
- if evaluation_id is not None:
28
- query = query.filter(Analysis.evaluation_id == evaluation_id)
29
- if analysis_type is not None:
30
- query = query.filter(Analysis.analysis_type == analysis_type)
31
- if id is not None:
32
- query = query.filter(Analysis.id == id)
33
- result = await session.execute(query)
34
- results = result.scalars().all()
35
- return [
36
- {k: v for k, v in result.__dict__.items() if not k.startswith("_")}
37
- for result in results
38
- ]
39
-
40
- async def create_analysis(
41
- self,
42
- evaluation_id: str,
43
- evaluation_analysis: dict,
44
- analysis_score_type: str = None,
45
- ):
46
- async with self.get_session() as session:
47
- for analysis in evaluation_analysis:
48
- analysis["evaluation_id"] = evaluation_id
49
- analysis["id"] = str(uuid.uuid4())
50
- instance = Analysis(**analysis)
51
- session.add(instance)
52
- await session.commit()
53
- await session.refresh(instance)
54
- async with self.score_client() as score_client:
55
- await score_client.update_ai_score(
56
- evaluation_id=evaluation_id, analysis_score_type=analysis_score_type
57
- )
58
- return await self.get_analysis(evaluation_id=evaluation_id)
59
-
60
- async def update_analysis(
61
- self, evaluation_id: str = None, id: str = None, proposal_analysis: dict = None
62
- ):
63
- async with self.get_session() as session:
64
- if id:
65
- query = select(Analysis).where(Analysis.id == id)
66
- result = await session.execute(query)
67
- instance = result.scalars().one()
68
- for k, v in proposal_analysis.items():
69
- setattr(instance, k, v)
70
- await session.commit()
71
- await session.refresh(instance)
72
- async with self.score_client() as score_client:
73
- await score_client.update_ai_score(evaluation_id=evaluation_id)
74
- return [
75
- {
76
- k: v
77
- for k, v in instance.__dict__.items()
78
- if not k.startswith("_")
79
- }
80
- ]
81
- else:
82
- await self.delete_analysis(evaluation_id=evaluation_id)
83
- return await self.create_analysis(
84
- evaluation_id=evaluation_id, evaluation_analysis=proposal_analysis
85
- )
86
-
87
- async def delete_analysis(self, evaluation_id: str = None, id: str = None):
88
- async with self.get_session() as session:
89
- query = select(Analysis)
90
- if evaluation_id:
91
- query = query.filter(Analysis.evaluation_id == evaluation_id)
92
- if id:
93
- query = query.filter(Analysis.id == id)
94
- result = await session.execute(query)
95
- instances = result.scalars().all()
96
- for instance in instances:
97
- await session.delete(instance)
98
- await session.commit()
99
- return True