Spaces:
Runtime error
Runtime error
Aryan Jain commited on
Commit ·
7a511fb
1
Parent(s): 63dfa98
update the apis according to the scores
Browse files- alembic/versions/3ba37b484f31_update_enum_type.py +56 -0
- alembic/versions/718e96a6869a_update_tables.py +125 -0
- src/controllers/__init__.py +8 -2
- src/controllers/_analysis_conteoller.py +239 -0
- src/controllers/_comparative_weight_controller.py +233 -0
- src/controllers/_evaluation_controller.py +35 -35
- src/controllers/_letter_controller.py +55 -46
- src/controllers/_proposal_controller.py +45 -35
- src/controllers/_proposal_evaluation_controller.py +202 -0
- src/controllers/_rfp_controller.py +34 -7
- src/models/__init__.py +9 -0
- src/models/_analysis.py +37 -0
- src/models/_comparitive_weights.py +45 -0
- src/models/_evaluation.py +8 -2
- src/models/_letters.py +10 -2
- src/models/_proposal.py +5 -0
- src/models/_proposal_evaluation.py +40 -0
- src/models/_rfp.py +5 -0
- src/repositories/__init__.py +6 -0
- src/repositories/_analysis_repository.py +92 -0
- src/repositories/_comparative_weight_repository.py +74 -0
- src/repositories/_proposal_evaluation_repository.py +92 -0
- src/services/__init__.py +6 -0
- src/services/_analysis_service.py +42 -0
- src/services/_comparative_weight_service.py +38 -0
- src/services/_evaluation_service.py +3 -1
- src/services/_proposal_ai_analysis_service.py +8 -2
- src/services/_proposal_evaluation_service.py +40 -0
- src/services/_proposal_service.py +3 -1
- src/utils/__init__.py +3 -2
- src/utils/_score_client.py +145 -0
alembic/versions/3ba37b484f31_update_enum_type.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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")
|
src/controllers/__init__.py
CHANGED
|
@@ -5,15 +5,21 @@ 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 |
|
| 9 |
api_router = APIRouter()
|
| 10 |
|
| 11 |
api_router.include_router(RFPController().router)
|
| 12 |
api_router.include_router(ProposalController().router)
|
| 13 |
-
api_router.include_router(ProposalAIController().router)
|
| 14 |
-
api_router.include_router(ProposalDetailedController().router)
|
| 15 |
api_router.include_router(LetterController().router)
|
| 16 |
api_router.include_router(EvaluationController().router)
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
__all__ = ["api_router"]
|
| 19 |
__version__ = "0.1.0"
|
|
|
|
| 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 |
|
| 12 |
api_router = APIRouter()
|
| 13 |
|
| 14 |
api_router.include_router(RFPController().router)
|
| 15 |
api_router.include_router(ProposalController().router)
|
| 16 |
+
# api_router.include_router(ProposalAIController().router)
|
| 17 |
+
# api_router.include_router(ProposalDetailedController().router)
|
| 18 |
api_router.include_router(LetterController().router)
|
| 19 |
api_router.include_router(EvaluationController().router)
|
| 20 |
+
api_router.include_router(ComparativeWeightController().router)
|
| 21 |
+
api_router.include_router(ProposalEvaluationController().router)
|
| 22 |
+
api_router.include_router(AnalysisController().router)
|
| 23 |
|
| 24 |
__all__ = ["api_router"]
|
| 25 |
__version__ = "0.1.0"
|
src/controllers/_analysis_conteoller.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
+
created_at: datetime
|
| 18 |
+
updated_at: datetime
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class Response(BaseModel):
|
| 22 |
+
status: str
|
| 23 |
+
analysis: Optional[List[Analysis]] = None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class CreateAnalysis(BaseModel):
|
| 27 |
+
insights: str
|
| 28 |
+
analysis_type: AnalysisType
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class CreateAnalysisRequest(BaseModel):
|
| 32 |
+
data: List[CreateAnalysis]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class UpdateAnalysis(BaseModel):
|
| 36 |
+
insights: Optional[str] = None
|
| 37 |
+
analysis_type: Optional[AnalysisType] = None
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class Deleteresponse(BaseModel):
|
| 41 |
+
status: str
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class AnalysisController:
|
| 45 |
+
def __init__(self):
|
| 46 |
+
self.service = AnalysisService
|
| 47 |
+
self.router = APIRouter()
|
| 48 |
+
self.router.add_api_route(
|
| 49 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
|
| 50 |
+
self.get_analysis_by_proposal_and_rfp_id_and_evaluation_id,
|
| 51 |
+
methods=["GET"],
|
| 52 |
+
response_model=Response,
|
| 53 |
+
tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
|
| 54 |
+
)
|
| 55 |
+
self.router.add_api_route(
|
| 56 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}",
|
| 57 |
+
self.get_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id,
|
| 58 |
+
methods=["GET"],
|
| 59 |
+
response_model=Response,
|
| 60 |
+
tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"],
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
self.router.add_api_route(
|
| 64 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
|
| 65 |
+
self.create_analysis_by_proposal_and_rfp_id_and_evaluation_id,
|
| 66 |
+
methods=["POST"],
|
| 67 |
+
response_model=Response,
|
| 68 |
+
tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
self.router.add_api_route(
|
| 72 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
|
| 73 |
+
self.update_analysis_by_proposal_and_rfp_id_and_evaluation_id,
|
| 74 |
+
methods=["PUT"],
|
| 75 |
+
response_model=Response,
|
| 76 |
+
tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
|
| 77 |
+
)
|
| 78 |
+
self.router.add_api_route(
|
| 79 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}",
|
| 80 |
+
self.update_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id,
|
| 81 |
+
methods=["PUT"],
|
| 82 |
+
response_model=Response,
|
| 83 |
+
tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"],
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
self.router.add_api_route(
|
| 87 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis",
|
| 88 |
+
self.delete_analysis_by_proposal_and_rfp_id_and_evaluation_id,
|
| 89 |
+
methods=["DELETE"],
|
| 90 |
+
response_model=Deleteresponse,
|
| 91 |
+
tags=["Analysis by Proposal and RFP ID and Evaluation ID"],
|
| 92 |
+
)
|
| 93 |
+
self.router.add_api_route(
|
| 94 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluations/{evaluation_id}/analysis/{id}",
|
| 95 |
+
self.delete_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id,
|
| 96 |
+
methods=["DELETE"],
|
| 97 |
+
response_model=Deleteresponse,
|
| 98 |
+
tags=["Analysis by Proposal and RFP ID and Evaluation ID and ID"],
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
async def get_analysis_by_proposal_and_rfp_id_and_evaluation_id(
|
| 102 |
+
self,
|
| 103 |
+
rfp_id: str = Path(...),
|
| 104 |
+
proposal_id: str = Path(...),
|
| 105 |
+
evaluation_id: str = Path(...),
|
| 106 |
+
analysis_type: AnalysisType = Query(None),
|
| 107 |
+
):
|
| 108 |
+
try:
|
| 109 |
+
async with self.service() as service:
|
| 110 |
+
results = await service.get_analysis(
|
| 111 |
+
evaluation_id=evaluation_id, analysis_type=analysis_type
|
| 112 |
+
)
|
| 113 |
+
return Response(
|
| 114 |
+
status="success",
|
| 115 |
+
analysis=[Analysis(**result) for result in results],
|
| 116 |
+
)
|
| 117 |
+
except Exception as e:
|
| 118 |
+
logger.error(e)
|
| 119 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 120 |
+
|
| 121 |
+
async def get_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id(
|
| 122 |
+
self,
|
| 123 |
+
rfp_id: str = Path(...),
|
| 124 |
+
proposal_id: str = Path(...),
|
| 125 |
+
evaluation_id: str = Path(...),
|
| 126 |
+
id: str = Path(...),
|
| 127 |
+
):
|
| 128 |
+
try:
|
| 129 |
+
async with self.service() as service:
|
| 130 |
+
results = await service.get_analysis(evaluation_id=evaluation_id, id=id)
|
| 131 |
+
return Response(
|
| 132 |
+
status="success",
|
| 133 |
+
analysis=[Analysis(**result) for result in results],
|
| 134 |
+
)
|
| 135 |
+
except Exception as e:
|
| 136 |
+
logger.error(e)
|
| 137 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 138 |
+
|
| 139 |
+
async def create_analysis_by_proposal_and_rfp_id_and_evaluation_id(
|
| 140 |
+
self,
|
| 141 |
+
rfp_id: str = Path(...),
|
| 142 |
+
proposal_id: str = Path(...),
|
| 143 |
+
evaluation_id: str = Path(...),
|
| 144 |
+
analysis: CreateAnalysisRequest = Body(...),
|
| 145 |
+
):
|
| 146 |
+
try:
|
| 147 |
+
async with self.service() as service:
|
| 148 |
+
results = await service.create_analysis(
|
| 149 |
+
evaluation_id=evaluation_id,
|
| 150 |
+
evaluation_analysis=analysis.model_dump(
|
| 151 |
+
mode="json", exclude_unset=True
|
| 152 |
+
)["data"],
|
| 153 |
+
)
|
| 154 |
+
return Response(
|
| 155 |
+
status="success",
|
| 156 |
+
analysis=[Analysis(**result) for result in results],
|
| 157 |
+
)
|
| 158 |
+
except Exception as e:
|
| 159 |
+
logger.error(e)
|
| 160 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 161 |
+
|
| 162 |
+
async def update_analysis_by_proposal_and_rfp_id_and_evaluation_id(
|
| 163 |
+
self,
|
| 164 |
+
rfp_id: str = Path(...),
|
| 165 |
+
proposal_id: str = Path(...),
|
| 166 |
+
evaluation_id: str = Path(...),
|
| 167 |
+
analysis: CreateAnalysisRequest = Body(...),
|
| 168 |
+
):
|
| 169 |
+
try:
|
| 170 |
+
async with self.service() as service:
|
| 171 |
+
results = await service.update_analysis(
|
| 172 |
+
evaluation_id=evaluation_id,
|
| 173 |
+
proposal_analysis=analysis.model_dump(
|
| 174 |
+
mode="json", exclude_unset=True
|
| 175 |
+
)["data"],
|
| 176 |
+
)
|
| 177 |
+
return Response(
|
| 178 |
+
status="success",
|
| 179 |
+
analysis=[Analysis(**result) for result in results],
|
| 180 |
+
)
|
| 181 |
+
except Exception as e:
|
| 182 |
+
logger.error(e)
|
| 183 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 184 |
+
|
| 185 |
+
async def update_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id(
|
| 186 |
+
self,
|
| 187 |
+
rfp_id: str = Path(...),
|
| 188 |
+
proposal_id: str = Path(...),
|
| 189 |
+
evaluation_id: str = Path(...),
|
| 190 |
+
id: str = Path(...),
|
| 191 |
+
analysis: UpdateAnalysis = Body(...),
|
| 192 |
+
):
|
| 193 |
+
try:
|
| 194 |
+
async with self.service() as service:
|
| 195 |
+
results = await service.update_analysis(
|
| 196 |
+
evaluation_id=evaluation_id,
|
| 197 |
+
id=id,
|
| 198 |
+
proposal_analysis=analysis.model_dump(
|
| 199 |
+
mode="json", exclude_unset=True
|
| 200 |
+
),
|
| 201 |
+
)
|
| 202 |
+
return Response(
|
| 203 |
+
status="success",
|
| 204 |
+
analysis=[Analysis(**result) for result in results],
|
| 205 |
+
)
|
| 206 |
+
except Exception as e:
|
| 207 |
+
logger.error(e)
|
| 208 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 209 |
+
|
| 210 |
+
async def delete_analysis_by_proposal_and_rfp_id_and_evaluation_id(
|
| 211 |
+
self,
|
| 212 |
+
rfp_id: str = Path(...),
|
| 213 |
+
proposal_id: str = Path(...),
|
| 214 |
+
evaluation_id: str = Path(...),
|
| 215 |
+
):
|
| 216 |
+
try:
|
| 217 |
+
async with self.service() as service:
|
| 218 |
+
results = await service.delete_analysis(evaluation_id=evaluation_id)
|
| 219 |
+
return Deleteresponse(status="success")
|
| 220 |
+
except Exception as e:
|
| 221 |
+
logger.error(e)
|
| 222 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 223 |
+
|
| 224 |
+
async def delete_analysis_by_proposal_and_rfp_id_and_evaluation_id_and_id(
|
| 225 |
+
self,
|
| 226 |
+
rfp_id: str = Path(...),
|
| 227 |
+
proposal_id: str = Path(...),
|
| 228 |
+
evaluation_id: str = Path(...),
|
| 229 |
+
id: str = Path(...),
|
| 230 |
+
):
|
| 231 |
+
try:
|
| 232 |
+
async with self.service() as service:
|
| 233 |
+
results = await service.delete_analysis(
|
| 234 |
+
evaluation_id=evaluation_id, id=id
|
| 235 |
+
)
|
| 236 |
+
return Deleteresponse(status="success")
|
| 237 |
+
except Exception as e:
|
| 238 |
+
logger.error(e)
|
| 239 |
+
raise HTTPException(status_code=500, detail=str(e))
|
src/controllers/_comparative_weight_controller.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 22 |
+
|
| 23 |
+
class ResponseComparativeWeight(BaseModel):
|
| 24 |
+
status: str
|
| 25 |
+
data: Optional[List[ComparitiveWeight]]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class DeleteResponse(BaseModel):
|
| 29 |
+
status: str
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class ComparativeWeightRequest(BaseModel):
|
| 33 |
+
technical_weight: Optional[Weights] = None
|
| 34 |
+
management_weight: Optional[Weights] = None
|
| 35 |
+
past_performance_weight: Optional[Weights] = None
|
| 36 |
+
price_weight: Optional[Weights] = None
|
| 37 |
+
strengths_weight: Optional[float] = None
|
| 38 |
+
weaknesses_weight: Optional[float] = None
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class ComparativeWeightController:
|
| 42 |
+
def __init__(self):
|
| 43 |
+
self.comparative_weight_service = ComparativeWeightService
|
| 44 |
+
self.router = APIRouter()
|
| 45 |
+
self.router.add_api_route(
|
| 46 |
+
"/rfps/{rfp_id}/comparative-weights",
|
| 47 |
+
self.get_comparative_weights_by_rfp_id,
|
| 48 |
+
methods=["GET"],
|
| 49 |
+
response_model=ResponseComparativeWeight,
|
| 50 |
+
tags=["Comparative Weights by RFP ID"],
|
| 51 |
+
)
|
| 52 |
+
self.router.add_api_route(
|
| 53 |
+
"/rfps/{rfp_id}/comparative-weights/{id}",
|
| 54 |
+
self.get_comparative_weights_by_id,
|
| 55 |
+
methods=["GET"],
|
| 56 |
+
response_model=ResponseComparativeWeight,
|
| 57 |
+
tags=["Comparative Weights by RFP ID and ID"],
|
| 58 |
+
)
|
| 59 |
+
self.router.add_api_route(
|
| 60 |
+
"/rfps/{rfp_id}/comparative-weights",
|
| 61 |
+
self.create_comparative_weights,
|
| 62 |
+
methods=["POST"],
|
| 63 |
+
response_model=ResponseComparativeWeight,
|
| 64 |
+
tags=["Comparative Weights by RFP ID"],
|
| 65 |
+
)
|
| 66 |
+
self.router.add_api_route(
|
| 67 |
+
"/rfps/{rfp_id}/comparative-weights/{id}",
|
| 68 |
+
self.update_comparative_weights_by_id,
|
| 69 |
+
methods=["PUT"],
|
| 70 |
+
response_model=ResponseComparativeWeight,
|
| 71 |
+
tags=["Comparative Weights by RFP ID and ID"],
|
| 72 |
+
)
|
| 73 |
+
self.router.add_api_route(
|
| 74 |
+
"/rfps/{rfp_id}/comparative-weights",
|
| 75 |
+
self.update_comparative_weights,
|
| 76 |
+
methods=["PUT"],
|
| 77 |
+
response_model=ResponseComparativeWeight,
|
| 78 |
+
tags=["Comparative Weights by RFP ID"],
|
| 79 |
+
)
|
| 80 |
+
self.router.add_api_route(
|
| 81 |
+
"/rfps/{rfp_id}/comparative-weights",
|
| 82 |
+
self.delete_comparative_weights,
|
| 83 |
+
methods=["DELETE"],
|
| 84 |
+
response_model=DeleteResponse,
|
| 85 |
+
tags=["Comparative Weights by RFP ID"],
|
| 86 |
+
)
|
| 87 |
+
self.router.add_api_route(
|
| 88 |
+
"/rfps/{rfp_id}/comparative-weights/{id}",
|
| 89 |
+
self.delete_comparative_weights_by_id,
|
| 90 |
+
methods=["DELETE"],
|
| 91 |
+
response_model=DeleteResponse,
|
| 92 |
+
tags=["Comparative Weights by RFP ID and ID"],
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
async def get_comparative_weights_by_rfp_id(self, rfp_id: str = Path(...)):
|
| 96 |
+
try:
|
| 97 |
+
async with self.comparative_weight_service() as service:
|
| 98 |
+
comparative_weights = await service.get_comparative_weights(
|
| 99 |
+
rfp_id=rfp_id
|
| 100 |
+
)
|
| 101 |
+
return ResponseComparativeWeight(
|
| 102 |
+
status="success",
|
| 103 |
+
data=[
|
| 104 |
+
ComparitiveWeight(**comparative_weight)
|
| 105 |
+
for comparative_weight in comparative_weights
|
| 106 |
+
],
|
| 107 |
+
)
|
| 108 |
+
except Exception as e:
|
| 109 |
+
logger.error(f"Error getting comparative weights by rfp id: {e}")
|
| 110 |
+
raise HTTPException(
|
| 111 |
+
status_code=500, detail="Error getting comparative weights by rfp id"
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
async def get_comparative_weights_by_id(
|
| 115 |
+
self, rfp_id: str = Path(...), id: str = Path(...)
|
| 116 |
+
):
|
| 117 |
+
try:
|
| 118 |
+
async with self.comparative_weight_service() as service:
|
| 119 |
+
comparative_weights = await service.get_comparative_weights(
|
| 120 |
+
rfp_id=rfp_id, id=id
|
| 121 |
+
)
|
| 122 |
+
return ResponseComparativeWeight(
|
| 123 |
+
status="success",
|
| 124 |
+
data=[
|
| 125 |
+
ComparitiveWeight(**comparative_weight)
|
| 126 |
+
for comparative_weight in comparative_weights
|
| 127 |
+
],
|
| 128 |
+
)
|
| 129 |
+
except Exception as e:
|
| 130 |
+
logger.error(f"Error getting comparative weights by id: {e}")
|
| 131 |
+
raise HTTPException(
|
| 132 |
+
status_code=500, detail="Error getting comparative weights by id"
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
async def create_comparative_weights(
|
| 136 |
+
self, comparative_weights: ComparativeWeightRequest, rfp_id: str = Path(...)
|
| 137 |
+
):
|
| 138 |
+
try:
|
| 139 |
+
async with self.comparative_weight_service() as service:
|
| 140 |
+
comparative_weights = await service.create_comparative_weights(
|
| 141 |
+
rfp_id=rfp_id,
|
| 142 |
+
comparative_weights=comparative_weights.model_dump(
|
| 143 |
+
mode="json", exclude_unset=True
|
| 144 |
+
),
|
| 145 |
+
)
|
| 146 |
+
return ResponseComparativeWeight(
|
| 147 |
+
status="success",
|
| 148 |
+
data=[
|
| 149 |
+
ComparitiveWeight(**comparative_weight)
|
| 150 |
+
for comparative_weight in comparative_weights
|
| 151 |
+
],
|
| 152 |
+
)
|
| 153 |
+
except Exception as e:
|
| 154 |
+
logger.error(f"Error creating comparative weights: {e}")
|
| 155 |
+
raise HTTPException(
|
| 156 |
+
status_code=500, detail="Error creating comparative weights"
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
async def update_comparative_weights(
|
| 160 |
+
self, comparative_weights: ComparativeWeightRequest, rfp_id: str = Path(...)
|
| 161 |
+
):
|
| 162 |
+
try:
|
| 163 |
+
async with self.comparative_weight_service() as service:
|
| 164 |
+
comparative_weights = await service.update_comparative_weights(
|
| 165 |
+
rfp_id=rfp_id,
|
| 166 |
+
comparative_weights=comparative_weights.model_dump(
|
| 167 |
+
mode="json", exclude_unset=True
|
| 168 |
+
),
|
| 169 |
+
)
|
| 170 |
+
return ResponseComparativeWeight(
|
| 171 |
+
status="success",
|
| 172 |
+
data=[
|
| 173 |
+
ComparitiveWeight(**comparative_weight)
|
| 174 |
+
for comparative_weight in comparative_weights
|
| 175 |
+
],
|
| 176 |
+
)
|
| 177 |
+
except Exception as e:
|
| 178 |
+
logger.error(f"Error updating comparative weights: {e}")
|
| 179 |
+
raise HTTPException(
|
| 180 |
+
status_code=500, detail="Error updating comparative weights"
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
async def update_comparative_weights_by_id(
|
| 184 |
+
self,
|
| 185 |
+
comparative_weights: ComparativeWeightRequest,
|
| 186 |
+
rfp_id: str = Path(...),
|
| 187 |
+
id: str = Path(...),
|
| 188 |
+
):
|
| 189 |
+
try:
|
| 190 |
+
async with self.comparative_weight_service() as service:
|
| 191 |
+
comparative_weights = await service.update_comparative_weights(
|
| 192 |
+
rfp_id=rfp_id,
|
| 193 |
+
id=id,
|
| 194 |
+
comparative_weights=comparative_weights.model_dump(
|
| 195 |
+
mode="json", exclude_unset=True
|
| 196 |
+
),
|
| 197 |
+
)
|
| 198 |
+
return ResponseComparativeWeight(
|
| 199 |
+
status="success",
|
| 200 |
+
data=[
|
| 201 |
+
ComparitiveWeight(**comparative_weight)
|
| 202 |
+
for comparative_weight in comparative_weights
|
| 203 |
+
],
|
| 204 |
+
)
|
| 205 |
+
except Exception as e:
|
| 206 |
+
logger.error(f"Error updating comparative weights by id: {e}")
|
| 207 |
+
raise HTTPException(
|
| 208 |
+
status_code=500, detail="Error updating comparative weights by id"
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
async def delete_comparative_weights(self, rfp_id: str = Path(...)):
|
| 212 |
+
try:
|
| 213 |
+
async with self.comparative_weight_service() as service:
|
| 214 |
+
await service.delete_comparative_weights(rfp_id=rfp_id)
|
| 215 |
+
return DeleteResponse(status="success")
|
| 216 |
+
except Exception as e:
|
| 217 |
+
logger.error(f"Error deleting comparative weights: {e}")
|
| 218 |
+
raise HTTPException(
|
| 219 |
+
status_code=500, detail="Error deleting comparative weights"
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
async def delete_comparative_weights_by_id(
|
| 223 |
+
self, rfp_id: str = Path(...), id: str = Path(...)
|
| 224 |
+
):
|
| 225 |
+
try:
|
| 226 |
+
async with self.comparative_weight_service() as service:
|
| 227 |
+
await service.delete_comparative_weights(rfp_id=rfp_id, id=id)
|
| 228 |
+
return DeleteResponse(status="success")
|
| 229 |
+
except Exception as e:
|
| 230 |
+
logger.error(f"Error deleting comparative weights: {e}")
|
| 231 |
+
raise HTTPException(
|
| 232 |
+
status_code=500, detail="Error deleting comparative weights"
|
| 233 |
+
)
|
src/controllers/_evaluation_controller.py
CHANGED
|
@@ -53,20 +53,20 @@ class EvaluationController:
|
|
| 53 |
def __init__(self):
|
| 54 |
self.evaluation_service = EvaluationService
|
| 55 |
self.router = APIRouter()
|
| 56 |
-
self.router.add_api_route(
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
)
|
| 63 |
-
self.router.add_api_route(
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
)
|
| 70 |
self.router.add_api_route(
|
| 71 |
"/rfps/{rfp_id}/evaluation-criteria",
|
| 72 |
self.get_criteria_by_rfp_id,
|
|
@@ -81,13 +81,13 @@ class EvaluationController:
|
|
| 81 |
response_model=EvaluationResponse,
|
| 82 |
tags=["Evaluation Criteria by Proposal and RFP ID"],
|
| 83 |
)
|
| 84 |
-
self.router.add_api_route(
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
)
|
| 91 |
self.router.add_api_route(
|
| 92 |
"/rfps/{rfp_id}/evaluation-criteria",
|
| 93 |
self.create_criteria_by_rfp_id,
|
|
@@ -95,13 +95,13 @@ class EvaluationController:
|
|
| 95 |
response_model=EvaluationResponse,
|
| 96 |
tags=["Evaluation Criteria by RFP ID"],
|
| 97 |
)
|
| 98 |
-
self.router.add_api_route(
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
)
|
| 105 |
self.router.add_api_route(
|
| 106 |
"/rfps/{rfp_id}/evaluation-criteria/{id}",
|
| 107 |
self.update_criteria_by_proposal_and_rfp_id,
|
|
@@ -109,13 +109,13 @@ class EvaluationController:
|
|
| 109 |
response_model=EvaluationResponse,
|
| 110 |
tags=["Evaluation Criteria by Proposal and RFP ID"],
|
| 111 |
)
|
| 112 |
-
self.router.add_api_route(
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
)
|
| 119 |
self.router.add_api_route(
|
| 120 |
"/rfps/{rfp_id}/evaluation-criteria",
|
| 121 |
self.delete_criteria_by_rfp_id,
|
|
|
|
| 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,
|
|
|
|
| 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,
|
|
|
|
| 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,
|
|
|
|
| 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,
|
src/controllers/_letter_controller.py
CHANGED
|
@@ -53,78 +53,78 @@ 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 |
-
"/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 |
-
"/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 |
-
"/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 |
-
"/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 |
-
"/
|
| 114 |
-
self.delete_letter,
|
| 115 |
-
methods=["DELETE"],
|
| 116 |
-
response_model=DeleteResponse,
|
| 117 |
-
tags=["Letters by ID"],
|
| 118 |
-
)
|
| 119 |
-
self.router.add_api_route(
|
| 120 |
-
"/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 |
-
"/proposals/{proposal_id}/letters/{id}",
|
| 128 |
self.delete_letters_by_proposal_and_id,
|
| 129 |
methods=["DELETE"],
|
| 130 |
response_model=DeleteResponse,
|
|
@@ -150,7 +150,10 @@ class LetterController:
|
|
| 150 |
raise HTTPException(status_code=500, detail="Error fetching letter")
|
| 151 |
|
| 152 |
async def get_letters_by_proposal_id(
|
| 153 |
-
self,
|
|
|
|
|
|
|
|
|
|
| 154 |
):
|
| 155 |
try:
|
| 156 |
async with self.letter_service() as service:
|
|
@@ -163,7 +166,7 @@ class LetterController:
|
|
| 163 |
raise HTTPException(status_code=500, detail="Error fetching letter")
|
| 164 |
|
| 165 |
async def get_letters_by_proposal_and_id(
|
| 166 |
-
self, proposal_id: str = Path(...), id: str = Path(...)
|
| 167 |
):
|
| 168 |
try:
|
| 169 |
async with self.letter_service() as service:
|
|
@@ -185,7 +188,10 @@ class LetterController:
|
|
| 185 |
raise HTTPException(status_code=500, detail="Error creating letter")
|
| 186 |
|
| 187 |
async def create_letters_by_proposal_id(
|
| 188 |
-
self,
|
|
|
|
|
|
|
|
|
|
| 189 |
):
|
| 190 |
try:
|
| 191 |
async with self.letter_service() as service:
|
|
@@ -213,6 +219,7 @@ class LetterController:
|
|
| 213 |
letter: UpdateLetterRequestByProposalAndId,
|
| 214 |
proposal_id: str = Path(...),
|
| 215 |
id: str = Path(...),
|
|
|
|
| 216 |
):
|
| 217 |
try:
|
| 218 |
async with self.letter_service() as service:
|
|
@@ -233,7 +240,9 @@ class LetterController:
|
|
| 233 |
logger.error(e)
|
| 234 |
raise HTTPException(status_code=500, detail="Error deleting letter")
|
| 235 |
|
| 236 |
-
async def delete_letters_by_proposal_id(
|
|
|
|
|
|
|
| 237 |
try:
|
| 238 |
async with self.letter_service() as service:
|
| 239 |
result = await service.delete_letter(proposal_id=proposal_id)
|
|
@@ -243,7 +252,7 @@ class LetterController:
|
|
| 243 |
raise HTTPException(status_code=500, detail="Error deleting letter")
|
| 244 |
|
| 245 |
async def delete_letters_by_proposal_and_id(
|
| 246 |
-
self, proposal_id: str = Path(...), id: str = Path(...)
|
| 247 |
):
|
| 248 |
try:
|
| 249 |
async with self.letter_service() as service:
|
|
|
|
| 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,
|
|
|
|
| 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:
|
|
|
|
| 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:
|
|
|
|
| 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:
|
|
|
|
| 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:
|
|
|
|
| 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)
|
|
|
|
| 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:
|
src/controllers/_proposal_controller.py
CHANGED
|
@@ -16,6 +16,8 @@ class Proposal(BaseModel):
|
|
| 16 |
tep: str
|
| 17 |
gate_criteria: GateCriteria
|
| 18 |
status: ProposalStatus
|
|
|
|
|
|
|
| 19 |
created_at: datetime
|
| 20 |
updated_at: datetime
|
| 21 |
|
|
@@ -26,6 +28,8 @@ class ProposalRequest(BaseModel):
|
|
| 26 |
tep: str
|
| 27 |
gate_criteria: GateCriteria
|
| 28 |
status: ProposalStatus
|
|
|
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
class CreateProposalRequest(BaseModel):
|
|
@@ -33,6 +37,8 @@ class CreateProposalRequest(BaseModel):
|
|
| 33 |
tep: str
|
| 34 |
gate_criteria: GateCriteria
|
| 35 |
status: ProposalStatus
|
|
|
|
|
|
|
| 36 |
|
| 37 |
|
| 38 |
class ProposalUpdateRequest(BaseModel):
|
|
@@ -41,6 +47,8 @@ class ProposalUpdateRequest(BaseModel):
|
|
| 41 |
tep: Optional[str] = None
|
| 42 |
gate_criteria: Optional[GateCriteria] = None
|
| 43 |
status: Optional[ProposalStatus] = None
|
|
|
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
class UpdateProposalRequest(BaseModel):
|
|
@@ -48,6 +56,8 @@ class UpdateProposalRequest(BaseModel):
|
|
| 48 |
tep: Optional[str] = None
|
| 49 |
gate_criteria: Optional[GateCriteria] = None
|
| 50 |
status: Optional[ProposalStatus] = None
|
|
|
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
class ProposalDeleteResponse(BaseModel):
|
|
@@ -63,20 +73,20 @@ class ProposalController:
|
|
| 63 |
def __init__(self):
|
| 64 |
self.__proposal_service = ProposalService
|
| 65 |
self.router = APIRouter()
|
| 66 |
-
self.router.add_api_route(
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
)
|
| 73 |
-
self.router.add_api_route(
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
)
|
| 80 |
self.router.add_api_route(
|
| 81 |
"/rfps/{rfp_id}/proposals",
|
| 82 |
self.get_proposals_by_rfp_id,
|
|
@@ -91,13 +101,13 @@ class ProposalController:
|
|
| 91 |
response_model=ResponseProposal,
|
| 92 |
tags=["Proposals by Proposal and RFP ID"],
|
| 93 |
)
|
| 94 |
-
self.router.add_api_route(
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
)
|
| 101 |
self.router.add_api_route(
|
| 102 |
"/rfps/{rfp_id}/proposals",
|
| 103 |
self.create_proposal_by_rfp_id,
|
|
@@ -105,13 +115,13 @@ class ProposalController:
|
|
| 105 |
response_model=ResponseProposal,
|
| 106 |
tags=["Proposals by RFP ID"],
|
| 107 |
)
|
| 108 |
-
self.router.add_api_route(
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
)
|
| 115 |
self.router.add_api_route(
|
| 116 |
"/rfps/{rfp_id}/proposals/{proposal_id}",
|
| 117 |
self.update_proposal_by_proposal_and_rfp_id,
|
|
@@ -119,13 +129,13 @@ class ProposalController:
|
|
| 119 |
response_model=ResponseProposal,
|
| 120 |
tags=["Proposals by Proposal and RFP ID"],
|
| 121 |
)
|
| 122 |
-
self.router.add_api_route(
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
)
|
| 129 |
self.router.add_api_route(
|
| 130 |
"/rfps/{rfp_id}/proposals",
|
| 131 |
self.delete_proposals_by_rfp_id,
|
|
|
|
| 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 |
|
|
|
|
| 28 |
tep: str
|
| 29 |
gate_criteria: GateCriteria
|
| 30 |
status: ProposalStatus
|
| 31 |
+
ai_score: Optional[float] = None
|
| 32 |
+
final_score: Optional[float] = None
|
| 33 |
|
| 34 |
|
| 35 |
class CreateProposalRequest(BaseModel):
|
|
|
|
| 37 |
tep: str
|
| 38 |
gate_criteria: GateCriteria
|
| 39 |
status: ProposalStatus
|
| 40 |
+
ai_score: Optional[float] = None
|
| 41 |
+
final_score: Optional[float] = None
|
| 42 |
|
| 43 |
|
| 44 |
class ProposalUpdateRequest(BaseModel):
|
|
|
|
| 47 |
tep: Optional[str] = None
|
| 48 |
gate_criteria: Optional[GateCriteria] = None
|
| 49 |
status: Optional[ProposalStatus] = None
|
| 50 |
+
ai_score: Optional[float] = None
|
| 51 |
+
final_score: Optional[float] = None
|
| 52 |
|
| 53 |
|
| 54 |
class UpdateProposalRequest(BaseModel):
|
|
|
|
| 56 |
tep: Optional[str] = None
|
| 57 |
gate_criteria: Optional[GateCriteria] = None
|
| 58 |
status: Optional[ProposalStatus] = None
|
| 59 |
+
ai_score: Optional[float] = None
|
| 60 |
+
final_score: Optional[float] = None
|
| 61 |
|
| 62 |
|
| 63 |
class ProposalDeleteResponse(BaseModel):
|
|
|
|
| 73 |
def __init__(self):
|
| 74 |
self.__proposal_service = ProposalService
|
| 75 |
self.router = APIRouter()
|
| 76 |
+
# self.router.add_api_route(
|
| 77 |
+
# "/proposals",
|
| 78 |
+
# self.get_proposals,
|
| 79 |
+
# methods=["GET"],
|
| 80 |
+
# response_model=ResponseProposal,
|
| 81 |
+
# tags=["Proposals"],
|
| 82 |
+
# )
|
| 83 |
+
# self.router.add_api_route(
|
| 84 |
+
# "/proposals/{proposal_id}",
|
| 85 |
+
# self.get_proposals_by_id,
|
| 86 |
+
# methods=["GET"],
|
| 87 |
+
# response_model=ResponseProposal,
|
| 88 |
+
# tags=["Proposals by ID"],
|
| 89 |
+
# )
|
| 90 |
self.router.add_api_route(
|
| 91 |
"/rfps/{rfp_id}/proposals",
|
| 92 |
self.get_proposals_by_rfp_id,
|
|
|
|
| 101 |
response_model=ResponseProposal,
|
| 102 |
tags=["Proposals by Proposal and RFP ID"],
|
| 103 |
)
|
| 104 |
+
# self.router.add_api_route(
|
| 105 |
+
# "/proposals",
|
| 106 |
+
# self.create_proposal,
|
| 107 |
+
# methods=["POST"],
|
| 108 |
+
# response_model=ResponseProposal,
|
| 109 |
+
# tags=["Proposals"],
|
| 110 |
+
# )
|
| 111 |
self.router.add_api_route(
|
| 112 |
"/rfps/{rfp_id}/proposals",
|
| 113 |
self.create_proposal_by_rfp_id,
|
|
|
|
| 115 |
response_model=ResponseProposal,
|
| 116 |
tags=["Proposals by RFP ID"],
|
| 117 |
)
|
| 118 |
+
# self.router.add_api_route(
|
| 119 |
+
# "/proposals/{proposal_id}",
|
| 120 |
+
# self.update_proposal,
|
| 121 |
+
# methods=["PUT"],
|
| 122 |
+
# response_model=ResponseProposal,
|
| 123 |
+
# tags=["Proposals by ID"],
|
| 124 |
+
# )
|
| 125 |
self.router.add_api_route(
|
| 126 |
"/rfps/{rfp_id}/proposals/{proposal_id}",
|
| 127 |
self.update_proposal_by_proposal_and_rfp_id,
|
|
|
|
| 129 |
response_model=ResponseProposal,
|
| 130 |
tags=["Proposals by Proposal and RFP ID"],
|
| 131 |
)
|
| 132 |
+
# self.router.add_api_route(
|
| 133 |
+
# "/proposals/{proposal_id}",
|
| 134 |
+
# self.delete_proposal,
|
| 135 |
+
# methods=["DELETE"],
|
| 136 |
+
# response_model=ProposalDeleteResponse,
|
| 137 |
+
# tags=["Proposals by ID"],
|
| 138 |
+
# )
|
| 139 |
self.router.add_api_route(
|
| 140 |
"/rfps/{rfp_id}/proposals",
|
| 141 |
self.delete_proposals_by_rfp_id,
|
src/controllers/_proposal_evaluation_controller.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 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 |
+
|
| 22 |
+
class Response(BaseModel):
|
| 23 |
+
status: str
|
| 24 |
+
evaluations: Optional[List[Evaluation]] = None
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class RequestEvaluation(BaseModel):
|
| 28 |
+
evaluation_type: Optional[EvaluationType] = None
|
| 29 |
+
ai_score: Optional[float] = None
|
| 30 |
+
adjusted_score: Optional[float] = None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class DeleteResponse(BaseModel):
|
| 34 |
+
status: str
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class CreateEvaluation(BaseModel):
|
| 38 |
+
evaluation_type: EvaluationType
|
| 39 |
+
ai_score: Optional[float] = None
|
| 40 |
+
adjusted_score: Optional[float] = None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class CreateEvaluationRequest(BaseModel):
|
| 44 |
+
data: List[CreateEvaluation]
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class UpdateEvaluation(BaseModel):
|
| 48 |
+
evaluation_type: Optional[EvaluationType] = None
|
| 49 |
+
ai_score: Optional[float] = None
|
| 50 |
+
adjusted_score: Optional[float] = None
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class ProposalEvaluationController:
|
| 54 |
+
def __init__(self):
|
| 55 |
+
self.service = ProposalEvaluationService
|
| 56 |
+
self.router = APIRouter()
|
| 57 |
+
self.router.add_api_route(
|
| 58 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluation",
|
| 59 |
+
self.get_evaluation_by_proposal_and_rfp_id,
|
| 60 |
+
methods=["GET"],
|
| 61 |
+
response_model=Response,
|
| 62 |
+
tags=["Evaluations by Proposal and RFP ID"],
|
| 63 |
+
)
|
| 64 |
+
self.router.add_api_route(
|
| 65 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluation/{id}",
|
| 66 |
+
self.get_evaluation_by_proposal_and_rfp_id_and_id,
|
| 67 |
+
methods=["GET"],
|
| 68 |
+
response_model=Response,
|
| 69 |
+
tags=["Evaluations by Proposal and RFP ID and ID"],
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
self.router.add_api_route(
|
| 73 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluation",
|
| 74 |
+
self.create_evaluation_by_proposal_and_rfp_id,
|
| 75 |
+
methods=["POST"],
|
| 76 |
+
response_model=Response,
|
| 77 |
+
tags=["Evaluations by Proposal and RFP ID"],
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
self.router.add_api_route(
|
| 81 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluation/{id}",
|
| 82 |
+
self.update_evaluation_by_proposal_and_rfp_id_and_id,
|
| 83 |
+
methods=["PUT"],
|
| 84 |
+
response_model=Response,
|
| 85 |
+
tags=["Evaluations by Proposal and RFP ID and ID"],
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
self.router.add_api_route(
|
| 89 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluation",
|
| 90 |
+
self.delete_evaluation_by_proposal_and_rfp_id,
|
| 91 |
+
methods=["DELETE"],
|
| 92 |
+
response_model=DeleteResponse,
|
| 93 |
+
tags=["Evaluations by Proposal and RFP ID"],
|
| 94 |
+
)
|
| 95 |
+
self.router.add_api_route(
|
| 96 |
+
"/rfps/{rfp_id}/proposals/{proposal_id}/evaluation/{id}",
|
| 97 |
+
self.delete_evaluation_by_proposal_and_rfp_id_and_id,
|
| 98 |
+
methods=["DELETE"],
|
| 99 |
+
response_model=DeleteResponse,
|
| 100 |
+
tags=["Evaluations by Proposal and RFP ID and ID"],
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
async def get_evaluation_by_proposal_and_rfp_id(
|
| 104 |
+
self,
|
| 105 |
+
rfp_id: str = Path(...),
|
| 106 |
+
proposal_id: str = Path(...),
|
| 107 |
+
evaluation_type: Optional[EvaluationType] = Query(None),
|
| 108 |
+
):
|
| 109 |
+
try:
|
| 110 |
+
async with self.service() as service:
|
| 111 |
+
results = await service.get_evaluations(
|
| 112 |
+
proposal_id=proposal_id, evaluation_type=evaluation_type
|
| 113 |
+
)
|
| 114 |
+
return Response(
|
| 115 |
+
status="success",
|
| 116 |
+
evaluations=[Evaluation(**result) for result in results],
|
| 117 |
+
)
|
| 118 |
+
except Exception as e:
|
| 119 |
+
logger.exception(e)
|
| 120 |
+
raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
|
| 121 |
+
|
| 122 |
+
async def get_evaluation_by_proposal_and_rfp_id_and_id(
|
| 123 |
+
self, rfp_id: str = Path(...), proposal_id: str = Path(...), id: str = Path(...)
|
| 124 |
+
):
|
| 125 |
+
try:
|
| 126 |
+
async with self.service() as service:
|
| 127 |
+
result = await service.get_evaluations(proposal_id=proposal_id, id=id)
|
| 128 |
+
return Response(
|
| 129 |
+
status="success",
|
| 130 |
+
evaluations=[Evaluation(**result) for result in result],
|
| 131 |
+
)
|
| 132 |
+
except Exception as e:
|
| 133 |
+
logger.exception(e)
|
| 134 |
+
raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
|
| 135 |
+
|
| 136 |
+
async def create_evaluation_by_proposal_and_rfp_id(
|
| 137 |
+
self,
|
| 138 |
+
evaluation: CreateEvaluationRequest,
|
| 139 |
+
rfp_id: str = Path(...),
|
| 140 |
+
proposal_id: str = Path(...),
|
| 141 |
+
):
|
| 142 |
+
try:
|
| 143 |
+
async with self.service() as service:
|
| 144 |
+
result = await service.create_evaluations(
|
| 145 |
+
proposal_id=proposal_id,
|
| 146 |
+
evaluations=evaluation.model_dump(mode="json", exclude_unset=True)[
|
| 147 |
+
"data"
|
| 148 |
+
],
|
| 149 |
+
)
|
| 150 |
+
return Response(
|
| 151 |
+
status="success",
|
| 152 |
+
evaluations=[Evaluation(**result) for result in result],
|
| 153 |
+
)
|
| 154 |
+
except Exception as e:
|
| 155 |
+
logger.exception(e)
|
| 156 |
+
raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
|
| 157 |
+
|
| 158 |
+
async def update_evaluation_by_proposal_and_rfp_id_and_id(
|
| 159 |
+
self,
|
| 160 |
+
evaluation: UpdateEvaluation,
|
| 161 |
+
rfp_id: str = Path(...),
|
| 162 |
+
proposal_id: str = Path(...),
|
| 163 |
+
id: str = Path(...),
|
| 164 |
+
):
|
| 165 |
+
try:
|
| 166 |
+
async with self.service() as service:
|
| 167 |
+
result = await service.update_evaluations(
|
| 168 |
+
proposal_id=proposal_id,
|
| 169 |
+
id=id,
|
| 170 |
+
evaluations=evaluation.model_dump(mode="json", exclude_unset=True),
|
| 171 |
+
)
|
| 172 |
+
return Response(
|
| 173 |
+
status="success",
|
| 174 |
+
evaluations=[Evaluation(**result) for result in result],
|
| 175 |
+
)
|
| 176 |
+
except Exception as e:
|
| 177 |
+
logger.exception(e)
|
| 178 |
+
raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
|
| 179 |
+
|
| 180 |
+
async def delete_evaluation_by_proposal_and_rfp_id(
|
| 181 |
+
self, rfp_id: str = Path(...), proposal_id: str = Path(...)
|
| 182 |
+
):
|
| 183 |
+
try:
|
| 184 |
+
async with self.service() as service:
|
| 185 |
+
result = await service.delete_evaluations(proposal_id=proposal_id)
|
| 186 |
+
return DeleteResponse(status="success")
|
| 187 |
+
except Exception as e:
|
| 188 |
+
logger.exception(e)
|
| 189 |
+
raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
|
| 190 |
+
|
| 191 |
+
async def delete_evaluation_by_proposal_and_rfp_id_and_id(
|
| 192 |
+
self, rfp_id: str = Path(...), proposal_id: str = Path(...), id: str = Path(...)
|
| 193 |
+
):
|
| 194 |
+
try:
|
| 195 |
+
async with self.service() as service:
|
| 196 |
+
result = await service.delete_evaluations(
|
| 197 |
+
proposal_id=proposal_id, id=id
|
| 198 |
+
)
|
| 199 |
+
return DeleteResponse(status="success")
|
| 200 |
+
except Exception as e:
|
| 201 |
+
logger.exception(e)
|
| 202 |
+
raise HTTPException(status_code=500, detail="Failed to retrieve proposals.")
|
src/controllers/_rfp_controller.py
CHANGED
|
@@ -19,6 +19,9 @@ class RFP(BaseModel):
|
|
| 19 |
evaluated_count: int
|
| 20 |
awaiting_evaluation: int
|
| 21 |
target_award_date: Optional[datetime] = None
|
|
|
|
|
|
|
|
|
|
| 22 |
status: RFPStatus
|
| 23 |
created_at: datetime
|
| 24 |
updated_at: datetime
|
|
@@ -34,7 +37,10 @@ class RFPRequest(BaseModel):
|
|
| 34 |
name: str
|
| 35 |
received_proposals: int
|
| 36 |
evaluated_count: int
|
| 37 |
-
target_award_date: datetime
|
|
|
|
|
|
|
|
|
|
| 38 |
status: RFPStatus
|
| 39 |
awaiting_evaluation: int
|
| 40 |
|
|
@@ -45,6 +51,9 @@ class RFPUpdateRequest(BaseModel):
|
|
| 45 |
received_proposals: Optional[int] = None
|
| 46 |
evaluated_count: Optional[int] = None
|
| 47 |
target_award_date: Optional[datetime] = None
|
|
|
|
|
|
|
|
|
|
| 48 |
status: Optional[RFPStatus] = None
|
| 49 |
awaiting_evaluation: Optional[int] = None
|
| 50 |
|
|
@@ -58,16 +67,32 @@ class RFPController:
|
|
| 58 |
self.__rfp_service = RFPService
|
| 59 |
self.router = APIRouter()
|
| 60 |
self.router.add_api_route(
|
| 61 |
-
"/rfps",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
)
|
| 63 |
self.router.add_api_route(
|
| 64 |
-
"/rfps/{rfp_id}",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
)
|
| 66 |
self.router.add_api_route(
|
| 67 |
-
"/rfps",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
)
|
| 69 |
self.router.add_api_route(
|
| 70 |
-
"/rfps/{rfp_id}",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
)
|
| 72 |
self.router.add_api_route(
|
| 73 |
"/rfps/{rfp_id}",
|
|
@@ -88,7 +113,7 @@ class RFPController:
|
|
| 88 |
except Exception as e:
|
| 89 |
logger.exception(e)
|
| 90 |
raise HTTPException(status_code=500, detail="Failed to retrieve RFPs.")
|
| 91 |
-
|
| 92 |
async def get_rfps_by_id(self, rfp_id: str = Path(...)):
|
| 93 |
async with self.__rfp_service() as service:
|
| 94 |
try:
|
|
@@ -116,7 +141,9 @@ class RFPController:
|
|
| 116 |
async def update_rfp(self, rfp: RFPUpdateRequest, rfp_id: str = Path(...)):
|
| 117 |
async with self.__rfp_service() as service:
|
| 118 |
try:
|
| 119 |
-
rfp = await service.update_rfp(
|
|
|
|
|
|
|
| 120 |
return ResponseRFP(status="success", data=[RFP(**rfp)])
|
| 121 |
except HTTPException as e:
|
| 122 |
logger.warning(e)
|
|
|
|
| 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 |
created_at: datetime
|
| 27 |
updated_at: datetime
|
|
|
|
| 37 |
name: str
|
| 38 |
received_proposals: int
|
| 39 |
evaluated_count: int
|
| 40 |
+
target_award_date: Optional[datetime] = None
|
| 41 |
+
ko_name: Optional[str] = None
|
| 42 |
+
award_type: Optional[str] = None
|
| 43 |
+
google_drive_id: Optional[str] = None
|
| 44 |
status: RFPStatus
|
| 45 |
awaiting_evaluation: int
|
| 46 |
|
|
|
|
| 51 |
received_proposals: Optional[int] = None
|
| 52 |
evaluated_count: Optional[int] = None
|
| 53 |
target_award_date: Optional[datetime] = None
|
| 54 |
+
ko_name: Optional[str] = None
|
| 55 |
+
award_type: Optional[str] = None
|
| 56 |
+
google_drive_id: Optional[str] = None
|
| 57 |
status: Optional[RFPStatus] = None
|
| 58 |
awaiting_evaluation: Optional[int] = None
|
| 59 |
|
|
|
|
| 67 |
self.__rfp_service = RFPService
|
| 68 |
self.router = APIRouter()
|
| 69 |
self.router.add_api_route(
|
| 70 |
+
"/rfps",
|
| 71 |
+
self.get_rfps,
|
| 72 |
+
methods=["GET"],
|
| 73 |
+
response_model=ResponseRFP,
|
| 74 |
+
tags=["RFPs"],
|
| 75 |
)
|
| 76 |
self.router.add_api_route(
|
| 77 |
+
"/rfps/{rfp_id}",
|
| 78 |
+
self.get_rfps_by_id,
|
| 79 |
+
methods=["GET"],
|
| 80 |
+
response_model=ResponseRFP,
|
| 81 |
+
tags=["RFPs by ID"],
|
| 82 |
)
|
| 83 |
self.router.add_api_route(
|
| 84 |
+
"/rfps",
|
| 85 |
+
self.create_rfp,
|
| 86 |
+
methods=["POST"],
|
| 87 |
+
response_model=ResponseRFP,
|
| 88 |
+
tags=["RFPs"],
|
| 89 |
)
|
| 90 |
self.router.add_api_route(
|
| 91 |
+
"/rfps/{rfp_id}",
|
| 92 |
+
self.update_rfp,
|
| 93 |
+
methods=["PUT"],
|
| 94 |
+
response_model=ResponseRFP,
|
| 95 |
+
tags=["RFPs by ID"],
|
| 96 |
)
|
| 97 |
self.router.add_api_route(
|
| 98 |
"/rfps/{rfp_id}",
|
|
|
|
| 113 |
except Exception as e:
|
| 114 |
logger.exception(e)
|
| 115 |
raise HTTPException(status_code=500, detail="Failed to retrieve RFPs.")
|
| 116 |
+
|
| 117 |
async def get_rfps_by_id(self, rfp_id: str = Path(...)):
|
| 118 |
async with self.__rfp_service() as service:
|
| 119 |
try:
|
|
|
|
| 141 |
async def update_rfp(self, rfp: RFPUpdateRequest, rfp_id: str = Path(...)):
|
| 142 |
async with self.__rfp_service() as service:
|
| 143 |
try:
|
| 144 |
+
rfp = await service.update_rfp(
|
| 145 |
+
rfp_id=rfp_id, rfp=rfp.model_dump(exclude_unset=True)
|
| 146 |
+
)
|
| 147 |
return ResponseRFP(status="success", data=[RFP(**rfp)])
|
| 148 |
except HTTPException as e:
|
| 149 |
logger.warning(e)
|
src/models/__init__.py
CHANGED
|
@@ -13,6 +13,9 @@ from ._proposal_detailed_analysis import ProposalDetailAnalysis, OperationCriter
|
|
| 13 |
from ._proposal_query_models import QueryType, CreateOrUpdateType
|
| 14 |
from ._letters import Letter, LetterType
|
| 15 |
from ._evaluation import EvaluationCriteria, EvaluationCriteriaType
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
__all__ = [
|
| 18 |
"Base",
|
|
@@ -35,6 +38,12 @@ __all__ = [
|
|
| 35 |
"EvaluationCriteriaType",
|
| 36 |
"RFPStatus",
|
| 37 |
"Category",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
]
|
| 39 |
__version__ = "0.1.0"
|
| 40 |
__author__ = "Aryan Jain"
|
|
|
|
| 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
|
| 19 |
|
| 20 |
__all__ = [
|
| 21 |
"Base",
|
|
|
|
| 38 |
"EvaluationCriteriaType",
|
| 39 |
"RFPStatus",
|
| 40 |
"Category",
|
| 41 |
+
"ComparativeWeights",
|
| 42 |
+
"Weights",
|
| 43 |
+
"Evaluations",
|
| 44 |
+
"EvaluationType",
|
| 45 |
+
"Analysis",
|
| 46 |
+
"AnalysisType",
|
| 47 |
]
|
| 48 |
__version__ = "0.1.0"
|
| 49 |
__author__ = "Aryan Jain"
|
src/models/_analysis.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 24 |
+
class Analysis(Base):
|
| 25 |
+
__tablename__ = "analysis"
|
| 26 |
+
id = Column(UUID(as_uuid=True), primary_key=True)
|
| 27 |
+
evaluation_id = Column(
|
| 28 |
+
UUID(as_uuid=True),
|
| 29 |
+
ForeignKey("evaluations.id", ondelete="CASCADE"),
|
| 30 |
+
nullable=False,
|
| 31 |
+
)
|
| 32 |
+
insights = Column(String, nullable=False)
|
| 33 |
+
analysis_type = Column(Enum(AnalysisType), nullable=False)
|
| 34 |
+
created_at = Column(DateTime(), nullable=False, default=func.now())
|
| 35 |
+
updated_at = Column(
|
| 36 |
+
DateTime(), nullable=False, default=func.now(), onupdate=func.now()
|
| 37 |
+
)
|
src/models/_comparitive_weights.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 Weights(PyEnum):
|
| 19 |
+
NONE = "NONE"
|
| 20 |
+
LOW = "LOW"
|
| 21 |
+
MEDIUM = "MEDIUM"
|
| 22 |
+
HIGH = "HIGH"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class ComparativeWeights(Base):
|
| 26 |
+
__tablename__ = "comparative_weights"
|
| 27 |
+
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
|
| 28 |
+
rfp_id = Column(
|
| 29 |
+
UUID(as_uuid=True),
|
| 30 |
+
ForeignKey("rfps.id", ondelete="CASCADE"),
|
| 31 |
+
nullable=False,
|
| 32 |
+
unique=True,
|
| 33 |
+
)
|
| 34 |
+
technical_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False)
|
| 35 |
+
management_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False)
|
| 36 |
+
past_performance_weight = Column(
|
| 37 |
+
Enum(Weights, name="comparativeweights"), nullable=False
|
| 38 |
+
)
|
| 39 |
+
price_weight = Column(Enum(Weights, name="comparativeweights"), nullable=False)
|
| 40 |
+
strengths_weight = Column(Float, nullable=False, default=15)
|
| 41 |
+
weaknesses_weight = Column(Float, nullable=False, default=5)
|
| 42 |
+
created_at = Column(DateTime, nullable=False, default=func.now())
|
| 43 |
+
updated_at = Column(
|
| 44 |
+
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
| 45 |
+
)
|
src/models/_evaluation.py
CHANGED
|
@@ -14,15 +14,21 @@ from sqlalchemy.dialects.postgresql import UUID
|
|
| 14 |
from pydantic import BaseModel
|
| 15 |
from ._base import Base
|
| 16 |
|
|
|
|
| 17 |
class EvaluationCriteriaType(PyEnum):
|
| 18 |
EVALUATION = "EVALUATION"
|
| 19 |
GATE = "GATE"
|
| 20 |
|
|
|
|
| 21 |
class EvaluationCriteria(Base):
|
| 22 |
__tablename__ = "evaluation_criteria_details"
|
| 23 |
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
|
| 24 |
-
rfp_id = Column(
|
|
|
|
|
|
|
| 25 |
evaluation_criteria = Column(String, nullable=True)
|
| 26 |
evaluation_criteria_type = Column(Enum(EvaluationCriteriaType), nullable=False)
|
| 27 |
created_at = Column(DateTime, nullable=False, default=func.now())
|
| 28 |
-
updated_at = Column(
|
|
|
|
|
|
|
|
|
| 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
CHANGED
|
@@ -14,15 +14,23 @@ from sqlalchemy.dialects.postgresql import UUID
|
|
| 14 |
from pydantic import BaseModel
|
| 15 |
from ._base import Base
|
| 16 |
|
|
|
|
| 17 |
class LetterType(PyEnum):
|
| 18 |
UO = "UO"
|
| 19 |
DEBRIEF = "DEBRIEF"
|
| 20 |
|
|
|
|
| 21 |
class Letter(Base):
|
| 22 |
__tablename__ = "letters"
|
| 23 |
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
|
| 24 |
-
proposal_id = Column(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
letter = Column(String, nullable=True)
|
| 26 |
letter_type = Column(Enum(LetterType), nullable=False)
|
| 27 |
created_at = Column(DateTime, nullable=False, default=func.now())
|
| 28 |
-
updated_at = Column(
|
|
|
|
|
|
|
|
|
| 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
CHANGED
|
@@ -14,16 +14,19 @@ from sqlalchemy.dialects.postgresql import UUID
|
|
| 14 |
from pydantic import BaseModel
|
| 15 |
from ._base import Base
|
| 16 |
|
|
|
|
| 17 |
class ProposalStatus(PyEnum):
|
| 18 |
APPROVED = "APPROVED"
|
| 19 |
EVALUATED = "EVALUATED"
|
| 20 |
IN_REVIEW = "IN_REVIEW"
|
| 21 |
|
|
|
|
| 22 |
class GateCriteria(PyEnum):
|
| 23 |
PASS = "PASS"
|
| 24 |
FAIL = "FAIL"
|
| 25 |
IN_REVIEW = "IN_REVIEW"
|
| 26 |
|
|
|
|
| 27 |
class Proposal(Base):
|
| 28 |
__tablename__ = "proposals"
|
| 29 |
|
|
@@ -35,6 +38,8 @@ class Proposal(Base):
|
|
| 35 |
tep = Column(String, nullable=False)
|
| 36 |
gate_criteria = Column(Enum(GateCriteria), nullable=False)
|
| 37 |
status = Column(Enum(ProposalStatus), nullable=False)
|
|
|
|
|
|
|
| 38 |
created_at = Column(DateTime, nullable=False, default=func.now())
|
| 39 |
updated_at = Column(
|
| 40 |
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
|
|
|
| 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 |
|
|
|
|
| 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()
|
src/models/_proposal_evaluation.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/_rfp.py
CHANGED
|
@@ -14,6 +14,7 @@ from sqlalchemy.dialects.postgresql import UUID
|
|
| 14 |
from pydantic import BaseModel
|
| 15 |
from ._base import Base
|
| 16 |
|
|
|
|
| 17 |
class RFPStatus(PyEnum):
|
| 18 |
ACTIVE = "ACTIVE"
|
| 19 |
IN_PROGRESS = "IN_PROGRESS"
|
|
@@ -23,6 +24,7 @@ class RFPStatus(PyEnum):
|
|
| 23 |
AWARDED = "AWARDED"
|
| 24 |
PROTESTED = "PROTESTED"
|
| 25 |
|
|
|
|
| 26 |
class RFP(Base):
|
| 27 |
__tablename__ = "rfps"
|
| 28 |
|
|
@@ -33,6 +35,9 @@ class RFP(Base):
|
|
| 33 |
evaluated_count = Column(Integer, nullable=False)
|
| 34 |
awaiting_evaluation = Column(Integer, nullable=False)
|
| 35 |
target_award_date = Column(DateTime, nullable=True)
|
|
|
|
|
|
|
|
|
|
| 36 |
status = Column(Enum(RFPStatus), nullable=False)
|
| 37 |
created_at = Column(DateTime, nullable=False, default=func.now())
|
| 38 |
updated_at = Column(
|
|
|
|
| 14 |
from pydantic import BaseModel
|
| 15 |
from ._base import Base
|
| 16 |
|
| 17 |
+
|
| 18 |
class RFPStatus(PyEnum):
|
| 19 |
ACTIVE = "ACTIVE"
|
| 20 |
IN_PROGRESS = "IN_PROGRESS"
|
|
|
|
| 24 |
AWARDED = "AWARDED"
|
| 25 |
PROTESTED = "PROTESTED"
|
| 26 |
|
| 27 |
+
|
| 28 |
class RFP(Base):
|
| 29 |
__tablename__ = "rfps"
|
| 30 |
|
|
|
|
| 35 |
evaluated_count = Column(Integer, nullable=False)
|
| 36 |
awaiting_evaluation = Column(Integer, nullable=False)
|
| 37 |
target_award_date = Column(DateTime, nullable=True)
|
| 38 |
+
ko_name = Column(String, nullable=True)
|
| 39 |
+
award_type = Column(String, nullable=True)
|
| 40 |
+
google_drive_id = Column(String, nullable=True)
|
| 41 |
status = Column(Enum(RFPStatus), nullable=False)
|
| 42 |
created_at = Column(DateTime, nullable=False, default=func.now())
|
| 43 |
updated_at = Column(
|
src/repositories/__init__.py
CHANGED
|
@@ -4,6 +4,9 @@ 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 |
|
| 8 |
__all__ = [
|
| 9 |
"BaseRepository",
|
|
@@ -12,6 +15,9 @@ __all__ = [
|
|
| 12 |
"ProposalDetailedAnalysisRepository",
|
| 13 |
"LetterRepository",
|
| 14 |
"EvaluationRepository",
|
|
|
|
|
|
|
|
|
|
| 15 |
]
|
| 16 |
__version__ = "0.1.0"
|
| 17 |
__author__ = "Aryan Jain"
|
|
|
|
| 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",
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(self, evaluation_id: str, evaluation_analysis: dict):
|
| 41 |
+
async with self.get_session() as session:
|
| 42 |
+
for analysis in evaluation_analysis:
|
| 43 |
+
analysis["evaluation_id"] = evaluation_id
|
| 44 |
+
analysis["id"] = str(uuid.uuid4())
|
| 45 |
+
instance = Analysis(**analysis)
|
| 46 |
+
session.add(instance)
|
| 47 |
+
await session.commit()
|
| 48 |
+
await session.refresh(instance)
|
| 49 |
+
async with self.score_client() as score_client:
|
| 50 |
+
await score_client.update_ai_score(evaluation_id=evaluation_id)
|
| 51 |
+
return await self.get_analysis(evaluation_id=evaluation_id)
|
| 52 |
+
|
| 53 |
+
async def update_analysis(
|
| 54 |
+
self, evaluation_id: str = None, id: str = None, proposal_analysis: dict = None
|
| 55 |
+
):
|
| 56 |
+
async with self.get_session() as session:
|
| 57 |
+
if id:
|
| 58 |
+
query = select(Analysis).where(Analysis.id == id)
|
| 59 |
+
result = await session.execute(query)
|
| 60 |
+
instance = result.scalars().one()
|
| 61 |
+
for k, v in proposal_analysis.items():
|
| 62 |
+
setattr(instance, k, v)
|
| 63 |
+
await session.commit()
|
| 64 |
+
await session.refresh(instance)
|
| 65 |
+
async with self.score_client() as score_client:
|
| 66 |
+
await score_client.update_ai_score(evaluation_id=evaluation_id)
|
| 67 |
+
return [
|
| 68 |
+
{
|
| 69 |
+
k: v
|
| 70 |
+
for k, v in instance.__dict__.items()
|
| 71 |
+
if not k.startswith("_")
|
| 72 |
+
}
|
| 73 |
+
]
|
| 74 |
+
else:
|
| 75 |
+
await self.delete_analysis(evaluation_id=evaluation_id)
|
| 76 |
+
return await self.create_analysis(
|
| 77 |
+
evaluation_id=evaluation_id, evaluation_analysis=proposal_analysis
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
async def delete_analysis(self, evaluation_id: str = None, id: str = None):
|
| 81 |
+
async with self.get_session() as session:
|
| 82 |
+
query = select(Analysis)
|
| 83 |
+
if evaluation_id:
|
| 84 |
+
query = query.filter(Analysis.evaluation_id == evaluation_id)
|
| 85 |
+
if id:
|
| 86 |
+
query = query.filter(Analysis.id == id)
|
| 87 |
+
result = await session.execute(query)
|
| 88 |
+
instances = result.scalars().all()
|
| 89 |
+
for instance in instances:
|
| 90 |
+
await session.delete(instance)
|
| 91 |
+
await session.commit()
|
| 92 |
+
return True
|
src/repositories/_comparative_weight_repository.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uuid
|
| 2 |
+
from sqlalchemy import select
|
| 3 |
+
from ._base_repository import BaseRepository
|
| 4 |
+
from src.models import ComparativeWeights
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class ComparativeWeightRepository(BaseRepository):
|
| 8 |
+
def __init__(self):
|
| 9 |
+
super().__init__(model=ComparativeWeights)
|
| 10 |
+
|
| 11 |
+
async def __aenter__(self):
|
| 12 |
+
return self
|
| 13 |
+
|
| 14 |
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
| 15 |
+
pass
|
| 16 |
+
|
| 17 |
+
async def get_comparative_weights(self, rfp_id: str = None, id: str = None):
|
| 18 |
+
async with self.get_session() as session:
|
| 19 |
+
query = select(ComparativeWeights)
|
| 20 |
+
if rfp_id:
|
| 21 |
+
query = query.where(ComparativeWeights.rfp_id == rfp_id)
|
| 22 |
+
if id:
|
| 23 |
+
query = query.where(ComparativeWeights.id == id)
|
| 24 |
+
result = await session.execute(query)
|
| 25 |
+
results = result.scalars().all()
|
| 26 |
+
return [
|
| 27 |
+
{k: v for k, v in result.__dict__.items() if not k.startswith("_")}
|
| 28 |
+
for result in results
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
async def create_comparative_weights(self, rfp_id: str, comparative_weights: dict):
|
| 32 |
+
comparative_weights["rfp_id"] = rfp_id
|
| 33 |
+
comparative_weights["id"] = str(uuid.uuid4())
|
| 34 |
+
async with self.get_session() as session:
|
| 35 |
+
instance = ComparativeWeights(**comparative_weights)
|
| 36 |
+
session.add(instance)
|
| 37 |
+
await session.commit()
|
| 38 |
+
await session.refresh(instance)
|
| 39 |
+
return [
|
| 40 |
+
{k: v for k, v in instance.__dict__.items() if not k.startswith("_")}
|
| 41 |
+
]
|
| 42 |
+
|
| 43 |
+
async def update_comparative_weights(
|
| 44 |
+
self, id: str = None, rfp_id: str = None, comparative_weights: dict = None
|
| 45 |
+
):
|
| 46 |
+
async with self.get_session() as session:
|
| 47 |
+
query = select(ComparativeWeights)
|
| 48 |
+
if id:
|
| 49 |
+
query = query.where(ComparativeWeights.id == id)
|
| 50 |
+
if rfp_id:
|
| 51 |
+
query = query.where(ComparativeWeights.rfp_id == rfp_id)
|
| 52 |
+
output = await session.execute(query)
|
| 53 |
+
instance = output.scalars().one()
|
| 54 |
+
for k, v in comparative_weights.items():
|
| 55 |
+
setattr(instance, k, v)
|
| 56 |
+
await session.commit()
|
| 57 |
+
await session.refresh(instance)
|
| 58 |
+
return [
|
| 59 |
+
{k: v for k, v in instance.__dict__.items() if not k.startswith("_")}
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
async def delete_comparative_weights(self, id: str = None, rfp_id: str = None):
|
| 63 |
+
async with self.get_session() as session:
|
| 64 |
+
query = select(ComparativeWeights)
|
| 65 |
+
if id:
|
| 66 |
+
query = query.where(ComparativeWeights.id == id)
|
| 67 |
+
if rfp_id:
|
| 68 |
+
query = query.where(ComparativeWeights.rfp_id == rfp_id)
|
| 69 |
+
output = await session.execute(query)
|
| 70 |
+
instances = output.scalars().all()
|
| 71 |
+
for instance in instances:
|
| 72 |
+
await session.delete(instance)
|
| 73 |
+
await session.commit()
|
| 74 |
+
return True
|
src/repositories/_proposal_evaluation_repository.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uuid
|
| 2 |
+
from sqlalchemy import select
|
| 3 |
+
from ._base_repository import BaseRepository
|
| 4 |
+
from src.models import Evaluations, EvaluationType
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class ProposalEvaluationRepository(BaseRepository):
|
| 8 |
+
def __init__(self):
|
| 9 |
+
super().__init__(Evaluations)
|
| 10 |
+
|
| 11 |
+
async def __aenter__(self):
|
| 12 |
+
return self
|
| 13 |
+
|
| 14 |
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
| 15 |
+
pass
|
| 16 |
+
|
| 17 |
+
async def get_evaluations(
|
| 18 |
+
self,
|
| 19 |
+
proposal_id: str = None,
|
| 20 |
+
id: str = None,
|
| 21 |
+
evaluation_type: EvaluationType = None,
|
| 22 |
+
):
|
| 23 |
+
async with self.get_session() as session:
|
| 24 |
+
query = select(Evaluations)
|
| 25 |
+
if proposal_id is not None:
|
| 26 |
+
query = query.filter(Evaluations.proposal_id == proposal_id)
|
| 27 |
+
if id is not None:
|
| 28 |
+
query = query.filter(Evaluations.id == id)
|
| 29 |
+
if evaluation_type is not None:
|
| 30 |
+
query = query.filter(Evaluations.evaluation_type == evaluation_type)
|
| 31 |
+
result = await session.execute(query)
|
| 32 |
+
results = result.scalars().all()
|
| 33 |
+
return [
|
| 34 |
+
{k: v for k, v in result.__dict__.items() if not k.startswith("_")}
|
| 35 |
+
for result in results
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
async def craete_evaluations(self, proposal_id: str, proposal_evaluations: dict):
|
| 39 |
+
for evaluations in proposal_evaluations:
|
| 40 |
+
evaluations["proposal_id"] = proposal_id
|
| 41 |
+
evaluations["id"] = str(uuid.uuid4())
|
| 42 |
+
async with self.get_session() as session:
|
| 43 |
+
query = select(Evaluations).where(
|
| 44 |
+
Evaluations.proposal_id == proposal_id,
|
| 45 |
+
Evaluations.evaluation_type == evaluations["evaluation_type"],
|
| 46 |
+
)
|
| 47 |
+
result = await session.execute(query)
|
| 48 |
+
instances = result.scalars().all()
|
| 49 |
+
if instances:
|
| 50 |
+
for instance in instances:
|
| 51 |
+
await session.delete(instance)
|
| 52 |
+
await session.commit()
|
| 53 |
+
print(evaluations)
|
| 54 |
+
instance = Evaluations(**evaluations)
|
| 55 |
+
session.add(instance)
|
| 56 |
+
await session.commit()
|
| 57 |
+
await session.refresh(instance)
|
| 58 |
+
return await self.get_evaluations(proposal_id=proposal_id)
|
| 59 |
+
|
| 60 |
+
async def update_evaluations(self, proposal_id: str, id: str, evaluations: dict):
|
| 61 |
+
async with self.get_session() as session:
|
| 62 |
+
if "evaluation_type" in evaluations:
|
| 63 |
+
query = select(Evaluations).where(
|
| 64 |
+
Evaluations.proposal_id == proposal_id,
|
| 65 |
+
Evaluations.evaluation_type == evaluations["evaluation_type"],
|
| 66 |
+
)
|
| 67 |
+
result = await session.execute(query)
|
| 68 |
+
instance = result.scalars().all()
|
| 69 |
+
if instance:
|
| 70 |
+
return False
|
| 71 |
+
query = select(Evaluations).where(Evaluations.id == id)
|
| 72 |
+
result = await session.execute(query)
|
| 73 |
+
instance = result.scalars().one()
|
| 74 |
+
for k, v in evaluations.items():
|
| 75 |
+
setattr(instance, k, v)
|
| 76 |
+
await session.commit()
|
| 77 |
+
await session.refresh(instance)
|
| 78 |
+
return [{k: v for k, v in instance.__dict__.items() if not k.startswith("_")}]
|
| 79 |
+
|
| 80 |
+
async def delete_evaluations(self, proposal_id: str = None, id: str = None):
|
| 81 |
+
async with self.get_session() as session:
|
| 82 |
+
query = select(Evaluations)
|
| 83 |
+
if proposal_id:
|
| 84 |
+
query = query.where(Evaluations.proposal_id == proposal_id)
|
| 85 |
+
if id:
|
| 86 |
+
query = query.where(Evaluations.id == id)
|
| 87 |
+
result = await session.execute(query)
|
| 88 |
+
instances = result.scalars().all()
|
| 89 |
+
for instance in instances:
|
| 90 |
+
await session.delete(instance)
|
| 91 |
+
await session.commit()
|
| 92 |
+
return True
|
src/services/__init__.py
CHANGED
|
@@ -8,6 +8,9 @@ from ._proposal_ai_analysis_service import (
|
|
| 8 |
from ._proposal_detailed_analysis_service import ProposalDetailedAnalysisService
|
| 9 |
from ._letter_service import LetterService
|
| 10 |
from ._evaluation_service import EvaluationService
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
__all__ = [
|
| 13 |
"RFPService",
|
|
@@ -18,6 +21,9 @@ __all__ = [
|
|
| 18 |
"ProposalDetailedAnalysisService",
|
| 19 |
"LetterService",
|
| 20 |
"EvaluationService",
|
|
|
|
|
|
|
|
|
|
| 21 |
]
|
| 22 |
__version__ = "0.1.0"
|
| 23 |
__author__ = "Aryan Jain"
|
|
|
|
| 8 |
from ._proposal_detailed_analysis_service import ProposalDetailedAnalysisService
|
| 9 |
from ._letter_service import LetterService
|
| 10 |
from ._evaluation_service import EvaluationService
|
| 11 |
+
from ._comparative_weight_service import ComparativeWeightService
|
| 12 |
+
from ._proposal_evaluation_service import ProposalEvaluationService
|
| 13 |
+
from ._analysis_service import AnalysisService
|
| 14 |
|
| 15 |
__all__ = [
|
| 16 |
"RFPService",
|
|
|
|
| 21 |
"ProposalDetailedAnalysisService",
|
| 22 |
"LetterService",
|
| 23 |
"EvaluationService",
|
| 24 |
+
"ComparativeWeightService",
|
| 25 |
+
"ProposalEvaluationService",
|
| 26 |
+
"AnalysisService",
|
| 27 |
]
|
| 28 |
__version__ = "0.1.0"
|
| 29 |
__author__ = "Aryan Jain"
|
src/services/_analysis_service.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.repositories import AnalysisRepository
|
| 2 |
+
from src.models import AnalysisType
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class AnalysisService:
|
| 6 |
+
def __init__(self):
|
| 7 |
+
self.analysis_repository = AnalysisRepository
|
| 8 |
+
|
| 9 |
+
async def __aenter__(self):
|
| 10 |
+
return self
|
| 11 |
+
|
| 12 |
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
| 13 |
+
pass
|
| 14 |
+
|
| 15 |
+
async def get_analysis(
|
| 16 |
+
self,
|
| 17 |
+
evaluation_id: str = None,
|
| 18 |
+
analysis_type: AnalysisType = None,
|
| 19 |
+
id: str = None,
|
| 20 |
+
):
|
| 21 |
+
async with self.analysis_repository() as repo:
|
| 22 |
+
return await repo.get_analysis(
|
| 23 |
+
evaluation_id=evaluation_id, analysis_type=analysis_type, id=id
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
async def create_analysis(self, evaluation_id: str, evaluation_analysis: dict):
|
| 27 |
+
async with self.analysis_repository() as repo:
|
| 28 |
+
return await repo.create_analysis(
|
| 29 |
+
evaluation_id=evaluation_id, evaluation_analysis=evaluation_analysis
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
async def update_analysis(
|
| 33 |
+
self, evaluation_id: str = None, id: str = None, proposal_analysis: dict = None
|
| 34 |
+
):
|
| 35 |
+
async with self.analysis_repository() as repo:
|
| 36 |
+
return await repo.update_analysis(
|
| 37 |
+
evaluation_id=evaluation_id, id=id, proposal_analysis=proposal_analysis
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
async def delete_analysis(self, evaluation_id: str = None, id: str = None):
|
| 41 |
+
async with self.analysis_repository() as repo:
|
| 42 |
+
return await repo.delete_analysis(evaluation_id=evaluation_id, id=id)
|
src/services/_comparative_weight_service.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.repositories import ComparativeWeightRepository
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class ComparativeWeightService:
|
| 5 |
+
def __init__(self):
|
| 6 |
+
self.comparative_weight_repository = ComparativeWeightRepository
|
| 7 |
+
|
| 8 |
+
async def __aenter__(self):
|
| 9 |
+
return self
|
| 10 |
+
|
| 11 |
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
| 12 |
+
pass
|
| 13 |
+
|
| 14 |
+
async def get_comparative_weights(self, rfp_id: str = None, id: str = None):
|
| 15 |
+
async with self.comparative_weight_repository() as comparative_weight_repository:
|
| 16 |
+
return await comparative_weight_repository.get_comparative_weights(
|
| 17 |
+
rfp_id=rfp_id, id=id
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
async def create_comparative_weights(self, rfp_id: str, comparative_weights: dict):
|
| 21 |
+
async with self.comparative_weight_repository() as comparative_weight_repository:
|
| 22 |
+
return await comparative_weight_repository.create_comparative_weights(
|
| 23 |
+
rfp_id=rfp_id, comparative_weights=comparative_weights
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
async def update_comparative_weights(
|
| 27 |
+
self, id: str = None, rfp_id: str = None, comparative_weights: dict = None
|
| 28 |
+
):
|
| 29 |
+
async with self.comparative_weight_repository() as comparative_weight_repository:
|
| 30 |
+
return await comparative_weight_repository.update_comparative_weights(
|
| 31 |
+
id=id, rfp_id=rfp_id, comparative_weights=comparative_weights
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
async def delete_comparative_weights(self, id: str = None, rfp_id: str = None):
|
| 35 |
+
async with self.comparative_weight_repository() as comparative_weight_repository:
|
| 36 |
+
return await comparative_weight_repository.delete_comparative_weights(
|
| 37 |
+
id=id, rfp_id=rfp_id
|
| 38 |
+
)
|
src/services/_evaluation_service.py
CHANGED
|
@@ -29,7 +29,9 @@ class EvaluationService:
|
|
| 29 |
|
| 30 |
async def update_criteria(self, id: str, evaluation_criteria: dict):
|
| 31 |
async with self._evaluation_repository() as repository:
|
| 32 |
-
return await repository.update_criteria(
|
|
|
|
|
|
|
| 33 |
|
| 34 |
async def delete_criteria(self, id: str = None, rfp_id: str = None):
|
| 35 |
async with self._evaluation_repository() as repository:
|
|
|
|
| 29 |
|
| 30 |
async def update_criteria(self, id: str, evaluation_criteria: dict):
|
| 31 |
async with self._evaluation_repository() as repository:
|
| 32 |
+
return await repository.update_criteria(
|
| 33 |
+
id=id, evaluation_criteria=evaluation_criteria
|
| 34 |
+
)
|
| 35 |
|
| 36 |
async def delete_criteria(self, id: str = None, rfp_id: str = None):
|
| 37 |
async with self._evaluation_repository() as repository:
|
src/services/_proposal_ai_analysis_service.py
CHANGED
|
@@ -27,7 +27,10 @@ class ProposalAIAnalysisService:
|
|
| 27 |
return await client.get_complete_analysis(proposal_id=proposal_id)
|
| 28 |
else:
|
| 29 |
return await client.get_all_data(
|
| 30 |
-
query_type=query_type,
|
|
|
|
|
|
|
|
|
|
| 31 |
)
|
| 32 |
|
| 33 |
async def create_data(
|
|
@@ -74,5 +77,8 @@ class ProposalAIAnalysisService:
|
|
| 74 |
return await client.delete_complete_analysis(proposal_id=proposal_id)
|
| 75 |
else:
|
| 76 |
return await client.delete_data(
|
| 77 |
-
query_type=query_type,
|
|
|
|
|
|
|
|
|
|
| 78 |
)
|
|
|
|
| 27 |
return await client.get_complete_analysis(proposal_id=proposal_id)
|
| 28 |
else:
|
| 29 |
return await client.get_all_data(
|
| 30 |
+
query_type=query_type,
|
| 31 |
+
id=id,
|
| 32 |
+
proposal_id=proposal_id,
|
| 33 |
+
category=category,
|
| 34 |
)
|
| 35 |
|
| 36 |
async def create_data(
|
|
|
|
| 77 |
return await client.delete_complete_analysis(proposal_id=proposal_id)
|
| 78 |
else:
|
| 79 |
return await client.delete_data(
|
| 80 |
+
query_type=query_type,
|
| 81 |
+
id=id,
|
| 82 |
+
proposal_id=proposal_id,
|
| 83 |
+
category=category,
|
| 84 |
)
|
src/services/_proposal_evaluation_service.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.repositories import ProposalEvaluationRepository
|
| 2 |
+
from src.models import EvaluationType
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class ProposalEvaluationService:
|
| 6 |
+
def __init__(self):
|
| 7 |
+
self.repo = ProposalEvaluationRepository
|
| 8 |
+
|
| 9 |
+
async def __aenter__(self):
|
| 10 |
+
return self
|
| 11 |
+
|
| 12 |
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
| 13 |
+
pass
|
| 14 |
+
|
| 15 |
+
async def get_evaluations(
|
| 16 |
+
self,
|
| 17 |
+
proposal_id: str = None,
|
| 18 |
+
id: str = None,
|
| 19 |
+
evaluation_type: EvaluationType = None,
|
| 20 |
+
):
|
| 21 |
+
async with self.repo() as repo:
|
| 22 |
+
return await repo.get_evaluations(
|
| 23 |
+
proposal_id=proposal_id, id=id, evaluation_type=evaluation_type
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
async def create_evaluations(self, proposal_id: str, evaluations: dict):
|
| 27 |
+
async with self.repo() as repo:
|
| 28 |
+
return await repo.craete_evaluations(
|
| 29 |
+
proposal_id=proposal_id, proposal_evaluations=evaluations
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
async def update_evaluations(self, proposal_id: str, id: str, evaluations: dict):
|
| 33 |
+
async with self.repo() as repo:
|
| 34 |
+
return await repo.update_evaluations(
|
| 35 |
+
proposal_id=proposal_id, id=id, evaluations=evaluations
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
async def delete_evaluations(self, proposal_id: str = None, id: str = None):
|
| 39 |
+
async with self.repo() as repo:
|
| 40 |
+
return await repo.delete_evaluations(proposal_id=proposal_id, id=id)
|
src/services/_proposal_service.py
CHANGED
|
@@ -30,7 +30,9 @@ class ProposalService:
|
|
| 30 |
|
| 31 |
async def update_proposal(self, proposal_id: str, proposal: dict):
|
| 32 |
async with self.proposal_repository() as repository:
|
| 33 |
-
return await repository.update_proposal(
|
|
|
|
|
|
|
| 34 |
|
| 35 |
async def delete_proposal(self, proposal_id: str = None, rfp_id: str = None):
|
| 36 |
async with self.proposal_repository() as repository:
|
|
|
|
| 30 |
|
| 31 |
async def update_proposal(self, proposal_id: str, proposal: dict):
|
| 32 |
async with self.proposal_repository() as repository:
|
| 33 |
+
return await repository.update_proposal(
|
| 34 |
+
proposal_id=proposal_id, proposal=proposal
|
| 35 |
+
)
|
| 36 |
|
| 37 |
async def delete_proposal(self, proposal_id: str = None, rfp_id: str = None):
|
| 38 |
async with self.proposal_repository() as repository:
|
src/utils/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from ._proposal_ai_analysis_client import ProposalAIAnalysisClient
|
|
|
|
| 2 |
|
| 3 |
-
__all__ = ["ProposalAIAnalysisClient"]
|
| 4 |
__version__ = "0.1.0"
|
| 5 |
-
__author__ = "Aryan Jain"
|
|
|
|
| 1 |
from ._proposal_ai_analysis_client import ProposalAIAnalysisClient
|
| 2 |
+
from ._score_client import ScoreClient
|
| 3 |
|
| 4 |
+
__all__ = ["ProposalAIAnalysisClient", "ScoreClient"]
|
| 5 |
__version__ = "0.1.0"
|
| 6 |
+
__author__ = "Aryan Jain"
|
src/utils/_score_client.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from uuid import UUID
|
| 2 |
+
import uuid
|
| 3 |
+
|
| 4 |
+
from sqlalchemy import select
|
| 5 |
+
from src.repositories import BaseRepository
|
| 6 |
+
|
| 7 |
+
from src.models import (
|
| 8 |
+
AnalysisType,
|
| 9 |
+
Analysis,
|
| 10 |
+
Proposal,
|
| 11 |
+
ComparativeWeights,
|
| 12 |
+
Weights,
|
| 13 |
+
Evaluations,
|
| 14 |
+
EvaluationType,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ScoreClient:
|
| 19 |
+
def __init__(self):
|
| 20 |
+
self.repository = BaseRepository
|
| 21 |
+
self.weighted_scores = {"NONE": 0, "LOW": 1, "MEDIUM": 3, "HIGH": 7}
|
| 22 |
+
|
| 23 |
+
async def __aenter__(self):
|
| 24 |
+
return self
|
| 25 |
+
|
| 26 |
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
| 27 |
+
pass
|
| 28 |
+
|
| 29 |
+
async def update_ai_score(self, evaluation_id: str):
|
| 30 |
+
async with self.repository(model=Evaluations).get_session() as session:
|
| 31 |
+
query = select(Evaluations).where(Evaluations.id == evaluation_id)
|
| 32 |
+
result = await session.execute(query)
|
| 33 |
+
evaluation = result.scalars().first()
|
| 34 |
+
proposal_id = evaluation.proposal_id
|
| 35 |
+
async with self.repository(model=Analysis).get_session() as session:
|
| 36 |
+
query = select(Analysis).where(Analysis.evaluation_id == evaluation_id)
|
| 37 |
+
result = await session.execute(query)
|
| 38 |
+
results = result.scalars().all()
|
| 39 |
+
deficiency = [
|
| 40 |
+
result.insights
|
| 41 |
+
for result in results
|
| 42 |
+
if result.analysis_type == AnalysisType.DEFICIENCIES
|
| 43 |
+
]
|
| 44 |
+
weaknesses = [
|
| 45 |
+
result.insights
|
| 46 |
+
for result in results
|
| 47 |
+
if result.analysis_type == AnalysisType.WEAKNESSES
|
| 48 |
+
]
|
| 49 |
+
strengths = [
|
| 50 |
+
result.insights
|
| 51 |
+
for result in results
|
| 52 |
+
if result.analysis_type == AnalysisType.STRENGTHS
|
| 53 |
+
]
|
| 54 |
+
start_score = 0 if deficiency else 100
|
| 55 |
+
|
| 56 |
+
async with self.repository(model=Proposal).get_session() as session:
|
| 57 |
+
query = select(Proposal).where(Proposal.id == proposal_id)
|
| 58 |
+
result = await session.execute(query)
|
| 59 |
+
proposal = result.scalars().first()
|
| 60 |
+
rfp_id = proposal.rfp_id
|
| 61 |
+
|
| 62 |
+
async with self.repository(model=ComparativeWeights).get_session() as session:
|
| 63 |
+
query = select(ComparativeWeights).where(
|
| 64 |
+
ComparativeWeights.rfp_id == rfp_id
|
| 65 |
+
)
|
| 66 |
+
result = await session.execute(query)
|
| 67 |
+
comparative_weights = result.scalars().first()
|
| 68 |
+
|
| 69 |
+
start_score += len(strengths) * comparative_weights.strengths_weight
|
| 70 |
+
start_score -= len(weaknesses) * comparative_weights.weaknesses_weight
|
| 71 |
+
|
| 72 |
+
async with self.repository(model=Evaluations).get_session() as session:
|
| 73 |
+
query = select(Evaluations).where(Evaluations.id == evaluation_id)
|
| 74 |
+
result = await session.execute(query)
|
| 75 |
+
evaluation = result.scalars().first()
|
| 76 |
+
evaluation.ai_score = start_score
|
| 77 |
+
await session.commit()
|
| 78 |
+
await session.refresh(evaluation)
|
| 79 |
+
query = select(Evaluations).where(Evaluations.proposal_id == proposal_id)
|
| 80 |
+
result = await session.execute(query)
|
| 81 |
+
evaluations = result.scalars().all()
|
| 82 |
+
|
| 83 |
+
technical_ai_score = [
|
| 84 |
+
evaluation.ai_score
|
| 85 |
+
for evaluation in evaluations
|
| 86 |
+
if evaluation.evaluation_type == EvaluationType.TECHNICAL
|
| 87 |
+
and evaluation.ai_score is not None
|
| 88 |
+
]
|
| 89 |
+
management_ai_score = [
|
| 90 |
+
evaluation.ai_score
|
| 91 |
+
for evaluation in evaluations
|
| 92 |
+
if evaluation.evaluation_type == EvaluationType.MANAGEMENT
|
| 93 |
+
and evaluation.ai_score is not None
|
| 94 |
+
]
|
| 95 |
+
past_performance_ai_score = [
|
| 96 |
+
evaluation.ai_score
|
| 97 |
+
for evaluation in evaluations
|
| 98 |
+
if evaluation.evaluation_type == EvaluationType.PAST_PERFORMANCE
|
| 99 |
+
and evaluation.ai_score is not None
|
| 100 |
+
]
|
| 101 |
+
price_ai_score = [
|
| 102 |
+
evaluation.ai_score
|
| 103 |
+
for evaluation in evaluations
|
| 104 |
+
if evaluation.evaluation_type == EvaluationType.PRICE
|
| 105 |
+
and evaluation.ai_score is not None
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
technical_ai_score = technical_ai_score[0] if technical_ai_score else 0
|
| 109 |
+
management_ai_score = management_ai_score[0] if management_ai_score else 0
|
| 110 |
+
past_performance_ai_score = (
|
| 111 |
+
past_performance_ai_score[0] if past_performance_ai_score else 0
|
| 112 |
+
)
|
| 113 |
+
price_ai_score = price_ai_score[0] if price_ai_score else 0
|
| 114 |
+
|
| 115 |
+
technical_weight = self.weighted_scores[
|
| 116 |
+
comparative_weights.technical_weight.name
|
| 117 |
+
]
|
| 118 |
+
management_weight = self.weighted_scores[
|
| 119 |
+
comparative_weights.management_weight.name
|
| 120 |
+
]
|
| 121 |
+
past_performance_weight = self.weighted_scores[
|
| 122 |
+
comparative_weights.past_performance_weight.name
|
| 123 |
+
]
|
| 124 |
+
price_weight = self.weighted_scores[comparative_weights.price_weight.name]
|
| 125 |
+
|
| 126 |
+
ai_score = (
|
| 127 |
+
technical_ai_score * technical_weight
|
| 128 |
+
+ management_ai_score * management_weight
|
| 129 |
+
+ past_performance_ai_score * past_performance_weight
|
| 130 |
+
+ price_ai_score * price_weight
|
| 131 |
+
)
|
| 132 |
+
ai_score /= (
|
| 133 |
+
technical_weight
|
| 134 |
+
+ management_weight
|
| 135 |
+
+ past_performance_weight
|
| 136 |
+
+ price_weight
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
async with self.repository(model=Proposal).get_session() as session:
|
| 140 |
+
query = select(Proposal).where(Proposal.id == proposal_id)
|
| 141 |
+
result = await session.execute(query)
|
| 142 |
+
evaluation = result.scalars().first()
|
| 143 |
+
evaluation.ai_score = ai_score
|
| 144 |
+
await session.commit()
|
| 145 |
+
await session.refresh(evaluation)
|