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 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
- "/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,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
- "/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,13 +95,13 @@ class EvaluationController:
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,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
- "/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,
 
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
- "/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
- "/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, proposal_id: str = Path(...), letter_type: LetterType = Query(None)
 
 
 
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, letter: CreateLetterRequest, proposal_id: str = Path(...)
 
 
 
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(self, proposal_id: str = Path(...)):
 
 
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
- "/proposals",
68
- self.get_proposals,
69
- methods=["GET"],
70
- response_model=ResponseProposal,
71
- tags=["Proposals"],
72
- )
73
- self.router.add_api_route(
74
- "/proposals/{proposal_id}",
75
- self.get_proposals_by_id,
76
- methods=["GET"],
77
- response_model=ResponseProposal,
78
- tags=["Proposals by ID"],
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
- "/proposals",
96
- self.create_proposal,
97
- methods=["POST"],
98
- response_model=ResponseProposal,
99
- tags=["Proposals"],
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
- "/proposals/{proposal_id}",
110
- self.update_proposal,
111
- methods=["PUT"],
112
- response_model=ResponseProposal,
113
- tags=["Proposals by ID"],
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
- "/proposals/{proposal_id}",
124
- self.delete_proposal,
125
- methods=["DELETE"],
126
- response_model=ProposalDeleteResponse,
127
- tags=["Proposals by ID"],
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", self.get_rfps, methods=["GET"], response_model=ResponseRFP, tags=["RFPs"]
 
 
 
 
62
  )
63
  self.router.add_api_route(
64
- "/rfps/{rfp_id}", self.get_rfps_by_id, methods=["GET"], response_model=ResponseRFP, tags=["RFPs by ID"]
 
 
 
 
65
  )
66
  self.router.add_api_route(
67
- "/rfps", self.create_rfp, methods=["POST"], response_model=ResponseRFP, tags=["RFPs"]
 
 
 
 
68
  )
69
  self.router.add_api_route(
70
- "/rfps/{rfp_id}", self.update_rfp, methods=["PUT"], response_model=ResponseRFP, tags=["RFPs by 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(rfp_id=rfp_id, rfp=rfp.model_dump(exclude_unset=True))
 
 
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(UUID(as_uuid=True), ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False)
 
 
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(DateTime, nullable=False, default=func.now(), onupdate=func.now())
 
 
 
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(UUID(as_uuid=True), ForeignKey("proposals.id", ondelete="CASCADE"), nullable=False)
 
 
 
 
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(DateTime, nullable=False, default=func.now(), onupdate=func.now())
 
 
 
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(id=id, evaluation_criteria=evaluation_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, id=id, proposal_id=proposal_id, category=category
 
 
 
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, id=id, proposal_id=proposal_id, category=category
 
 
 
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(proposal_id=proposal_id, proposal=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)