Spaces:
Runtime error
Runtime error
Aryan Jain commited on
Commit ·
ef8c55b
1
Parent(s): 8e1c132
update database schema
Browse files- alembic/versions/e825902b2e8a_alter_tables.py +49 -0
- src/controllers/__init__.py +2 -0
- src/controllers/_letter_controller.py +102 -0
- src/controllers/_proposal_controller.py +7 -1
- src/controllers/_rfp_controller.py +3 -0
- src/models/__init__.py +5 -1
- src/models/_letters.py +28 -0
- src/models/_proposal.py +6 -0
- src/models/_rfp.py +1 -0
- src/repositories/__init__.py +9 -2
- src/repositories/_letter_repository.py +79 -0
- src/services/__init__.py +2 -0
- src/services/_letter_service.py +28 -0
alembic/versions/e825902b2e8a_alter_tables.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""alter tables
|
| 2 |
+
|
| 3 |
+
Revision ID: e825902b2e8a
|
| 4 |
+
Revises: 11d43047d470
|
| 5 |
+
Create Date: 2025-06-02 16:32:45.552509
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
from typing import Sequence, Union
|
| 9 |
+
|
| 10 |
+
from alembic import op
|
| 11 |
+
import sqlalchemy as sa
|
| 12 |
+
|
| 13 |
+
from sqlalchemy.dialects import postgresql
|
| 14 |
+
|
| 15 |
+
# revision identifiers, used by Alembic.
|
| 16 |
+
revision: str = 'e825902b2e8a'
|
| 17 |
+
down_revision: Union[str, None] = '11d43047d470'
|
| 18 |
+
branch_labels: Union[str, Sequence[str], None] = None
|
| 19 |
+
depends_on: Union[str, Sequence[str], None] = None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def upgrade() -> None:
|
| 23 |
+
"""Upgrade schema."""
|
| 24 |
+
op.add_column("rfps", sa.Column("name", sa.String(), nullable=True))
|
| 25 |
+
|
| 26 |
+
op.add_column("proposals", sa.Column("tep", sa.String(), nullable=True))
|
| 27 |
+
|
| 28 |
+
gate_criteria_enum = sa.Enum('PASS', 'FAIL', 'IN_REVIEW', name='gatecriteria')
|
| 29 |
+
gate_criteria_enum.create(op.get_bind())
|
| 30 |
+
op.add_column('proposals', sa.Column('gate_criteria', gate_criteria_enum, nullable=False, server_default='IN_REVIEW'))
|
| 31 |
+
|
| 32 |
+
op.create_table(
|
| 33 |
+
"letters",
|
| 34 |
+
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
| 35 |
+
sa.Column("proposal_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("proposals.id", ondelete="CASCADE"), nullable=False),
|
| 36 |
+
sa.Column("letter", sa.Text(), nullable=True),
|
| 37 |
+
sa.Column("letter_type", sa.Enum("UO", "DEBRIEF", name="lettertype"), nullable=False),
|
| 38 |
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
| 39 |
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def downgrade() -> None:
|
| 44 |
+
"""Downgrade schema."""
|
| 45 |
+
op.drop_table("letters")
|
| 46 |
+
sa.Enum(name='gatecriteria').drop(op.get_bind(), checkfirst=True)
|
| 47 |
+
op.drop_column("proposals", "gate_criteria")
|
| 48 |
+
op.drop_column("proposals", "tep")
|
| 49 |
+
op.drop_column("rfps", "name")
|
src/controllers/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@ from ._rfp_controller import RFPController
|
|
| 3 |
from ._proposal_controller import ProposalController
|
| 4 |
from ._proposal_ai_analysis_controller import ProposalAIController
|
| 5 |
from ._proposal_detailed_analysis_controller import ProposalDetailedController
|
|
|
|
| 6 |
|
| 7 |
api_router = APIRouter()
|
| 8 |
|
|
@@ -10,6 +11,7 @@ api_router.include_router(RFPController().router, prefix="/rfp", tags=["RFP"])
|
|
| 10 |
api_router.include_router(ProposalController().router, prefix="/proposal", tags=["Proposal"])
|
| 11 |
api_router.include_router(ProposalAIController().router, prefix="/proposal_ai_analysis", tags=["Proposal AI Analysis"])
|
| 12 |
api_router.include_router(ProposalDetailedController().router, prefix="/proposal_detailed_analysis", tags=["Proposal Detailed Analysis"])
|
|
|
|
| 13 |
|
| 14 |
__all__ = ["api_router"]
|
| 15 |
__version__ = "0.1.0"
|
|
|
|
| 3 |
from ._proposal_controller import ProposalController
|
| 4 |
from ._proposal_ai_analysis_controller import ProposalAIController
|
| 5 |
from ._proposal_detailed_analysis_controller import ProposalDetailedController
|
| 6 |
+
from ._letter_controller import LetterController
|
| 7 |
|
| 8 |
api_router = APIRouter()
|
| 9 |
|
|
|
|
| 11 |
api_router.include_router(ProposalController().router, prefix="/proposal", tags=["Proposal"])
|
| 12 |
api_router.include_router(ProposalAIController().router, prefix="/proposal_ai_analysis", tags=["Proposal AI Analysis"])
|
| 13 |
api_router.include_router(ProposalDetailedController().router, prefix="/proposal_detailed_analysis", tags=["Proposal Detailed Analysis"])
|
| 14 |
+
api_router.include_router(LetterController().router, prefix="/letter", tags=["Letter"])
|
| 15 |
|
| 16 |
__all__ = ["api_router"]
|
| 17 |
__version__ = "0.1.0"
|
src/controllers/_letter_controller.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException, Query
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
from uuid import UUID
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
from src.config import logger
|
| 8 |
+
from src.services import LetterService
|
| 9 |
+
from src.models import LetterType
|
| 10 |
+
|
| 11 |
+
class Letter(BaseModel):
|
| 12 |
+
id: UUID
|
| 13 |
+
proposal_id: UUID
|
| 14 |
+
letter: str
|
| 15 |
+
letter_type: LetterType
|
| 16 |
+
created_at: datetime
|
| 17 |
+
updated_at: datetime
|
| 18 |
+
|
| 19 |
+
class Response(BaseModel):
|
| 20 |
+
status: str
|
| 21 |
+
data: List[Letter]
|
| 22 |
+
|
| 23 |
+
class LetterRequest(BaseModel):
|
| 24 |
+
proposal_id: UUID
|
| 25 |
+
letter: str
|
| 26 |
+
letter_type: LetterType
|
| 27 |
+
|
| 28 |
+
class UpdateLetterRequest(BaseModel):
|
| 29 |
+
id: UUID
|
| 30 |
+
proposal_id: Optional[UUID] = None
|
| 31 |
+
letter: Optional[str] = None
|
| 32 |
+
letter_type: Optional[LetterType] = None
|
| 33 |
+
|
| 34 |
+
class DeleteResponse(BaseModel):
|
| 35 |
+
status: str
|
| 36 |
+
|
| 37 |
+
class LetterController:
|
| 38 |
+
def __init__(self):
|
| 39 |
+
self.letter_service = LetterService
|
| 40 |
+
self.router = APIRouter()
|
| 41 |
+
self.router.add_api_route(
|
| 42 |
+
"/get_letters",
|
| 43 |
+
self.get_letters,
|
| 44 |
+
methods=["GET"],
|
| 45 |
+
response_model=Response
|
| 46 |
+
)
|
| 47 |
+
self.router.add_api_route(
|
| 48 |
+
"/create_letter",
|
| 49 |
+
self.create_letter,
|
| 50 |
+
methods=["POST"],
|
| 51 |
+
response_model=Response
|
| 52 |
+
)
|
| 53 |
+
self.router.add_api_route(
|
| 54 |
+
"/update_letter",
|
| 55 |
+
self.update_letter,
|
| 56 |
+
methods=["PUT"],
|
| 57 |
+
response_model=Response
|
| 58 |
+
)
|
| 59 |
+
self.router.add_api_route(
|
| 60 |
+
"/delete_letter",
|
| 61 |
+
self.delete_letter,
|
| 62 |
+
methods=["DELETE"],
|
| 63 |
+
response_model=DeleteResponse
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
async def get_letters(self, id: Optional[str] = Query(None), proposal_id: Optional[str] = Query(None), letter_type: Optional[LetterType] = Query(None)):
|
| 67 |
+
try:
|
| 68 |
+
async with self.letter_service() as service:
|
| 69 |
+
result = await service.get_letters(id=id, proposal_id=proposal_id, letter_type=letter_type)
|
| 70 |
+
return Response(status="success", data=[Letter(**r) for r in result])
|
| 71 |
+
except Exception as e:
|
| 72 |
+
logger.error(e)
|
| 73 |
+
raise HTTPException(status_code=500, detail="Error fetching letters")
|
| 74 |
+
|
| 75 |
+
async def create_letter(self, letter: LetterRequest):
|
| 76 |
+
try:
|
| 77 |
+
async with self.letter_service() as service:
|
| 78 |
+
result = await service.create_letter(letter.model_dump(exclude_unset=True, mode="json"))
|
| 79 |
+
return Response(status="success", data=[Letter(**r) for r in result])
|
| 80 |
+
except Exception as e:
|
| 81 |
+
logger.error(e)
|
| 82 |
+
raise HTTPException(status_code=500, detail="Error creating letter")
|
| 83 |
+
|
| 84 |
+
async def update_letter(self, letter: UpdateLetterRequest):
|
| 85 |
+
try:
|
| 86 |
+
async with self.letter_service() as service:
|
| 87 |
+
result = await service.update_letter(letter.model_dump(exclude_unset=True, mode="json"))
|
| 88 |
+
return Response(status="success", data=[Letter(**r) for r in result])
|
| 89 |
+
except Exception as e:
|
| 90 |
+
logger.error(e)
|
| 91 |
+
raise HTTPException(status_code=500, detail="Error updating letter")
|
| 92 |
+
|
| 93 |
+
async def delete_letter(self, id: Optional[str] = Query(None), proposal_id: Optional[str] = Query(None)):
|
| 94 |
+
try:
|
| 95 |
+
if not id and not proposal_id:
|
| 96 |
+
return DeleteResponse(status="Failed to delete letter")
|
| 97 |
+
async with self.letter_service() as service:
|
| 98 |
+
result = await service.delete_letter(id)
|
| 99 |
+
return DeleteResponse(status="success")
|
| 100 |
+
except Exception as e:
|
| 101 |
+
logger.error(e)
|
| 102 |
+
raise HTTPException(status_code=500, detail="Error deleting letter")
|
src/controllers/_proposal_controller.py
CHANGED
|
@@ -6,13 +6,15 @@ from datetime import datetime
|
|
| 6 |
|
| 7 |
from src.config import logger
|
| 8 |
from src.services import ProposalService
|
| 9 |
-
from src.models import ProposalStatus
|
| 10 |
|
| 11 |
|
| 12 |
class Proposal(BaseModel):
|
| 13 |
id: UUID
|
| 14 |
rfp_id: UUID
|
| 15 |
name: str
|
|
|
|
|
|
|
| 16 |
status: ProposalStatus
|
| 17 |
created_at: datetime
|
| 18 |
updated_at: datetime
|
|
@@ -21,6 +23,8 @@ class Proposal(BaseModel):
|
|
| 21 |
class ProposalRequest(BaseModel):
|
| 22 |
rfp_id: UUID
|
| 23 |
name: str
|
|
|
|
|
|
|
| 24 |
status: ProposalStatus
|
| 25 |
|
| 26 |
|
|
@@ -28,6 +32,8 @@ class ProposalUpdateRequest(BaseModel):
|
|
| 28 |
id: UUID
|
| 29 |
rfp_id: Optional[UUID] = None
|
| 30 |
name: Optional[str] = None
|
|
|
|
|
|
|
| 31 |
status: Optional[ProposalStatus] = None
|
| 32 |
|
| 33 |
|
|
|
|
| 6 |
|
| 7 |
from src.config import logger
|
| 8 |
from src.services import ProposalService
|
| 9 |
+
from src.models import ProposalStatus, GateCriteria
|
| 10 |
|
| 11 |
|
| 12 |
class Proposal(BaseModel):
|
| 13 |
id: UUID
|
| 14 |
rfp_id: UUID
|
| 15 |
name: str
|
| 16 |
+
tep: str
|
| 17 |
+
gate_criteria: GateCriteria
|
| 18 |
status: ProposalStatus
|
| 19 |
created_at: datetime
|
| 20 |
updated_at: datetime
|
|
|
|
| 23 |
class ProposalRequest(BaseModel):
|
| 24 |
rfp_id: UUID
|
| 25 |
name: str
|
| 26 |
+
tep: str
|
| 27 |
+
gate_criteria: GateCriteria
|
| 28 |
status: ProposalStatus
|
| 29 |
|
| 30 |
|
|
|
|
| 32 |
id: UUID
|
| 33 |
rfp_id: Optional[UUID] = None
|
| 34 |
name: Optional[str] = None
|
| 35 |
+
tep: Optional[str] = None
|
| 36 |
+
gate_criteria: Optional[GateCriteria] = None
|
| 37 |
status: Optional[ProposalStatus] = None
|
| 38 |
|
| 39 |
|
src/controllers/_rfp_controller.py
CHANGED
|
@@ -10,6 +10,7 @@ from datetime import datetime
|
|
| 10 |
class RFP(BaseModel):
|
| 11 |
id: UUID
|
| 12 |
rfp_number: str
|
|
|
|
| 13 |
received_proposals: int
|
| 14 |
evaluated_count: int
|
| 15 |
awaiting_evaluation: int
|
|
@@ -22,6 +23,7 @@ class ResponseRFP(BaseModel):
|
|
| 22 |
|
| 23 |
class RFPRequest(BaseModel):
|
| 24 |
rfp_number: str
|
|
|
|
| 25 |
received_proposals: int
|
| 26 |
evaluated_count: int
|
| 27 |
awaiting_evaluation: int
|
|
@@ -29,6 +31,7 @@ class RFPRequest(BaseModel):
|
|
| 29 |
class RFPUpdateRequest(BaseModel):
|
| 30 |
id: str
|
| 31 |
rfp_number: Optional[str] = None
|
|
|
|
| 32 |
received_proposals: Optional[int] = None
|
| 33 |
evaluated_count: Optional[int] = None
|
| 34 |
awaiting_evaluation: Optional[int] = None
|
|
|
|
| 10 |
class RFP(BaseModel):
|
| 11 |
id: UUID
|
| 12 |
rfp_number: str
|
| 13 |
+
name: str
|
| 14 |
received_proposals: int
|
| 15 |
evaluated_count: int
|
| 16 |
awaiting_evaluation: int
|
|
|
|
| 23 |
|
| 24 |
class RFPRequest(BaseModel):
|
| 25 |
rfp_number: str
|
| 26 |
+
name: str
|
| 27 |
received_proposals: int
|
| 28 |
evaluated_count: int
|
| 29 |
awaiting_evaluation: int
|
|
|
|
| 31 |
class RFPUpdateRequest(BaseModel):
|
| 32 |
id: str
|
| 33 |
rfp_number: Optional[str] = None
|
| 34 |
+
name: Optional[str] = None
|
| 35 |
received_proposals: Optional[int] = None
|
| 36 |
evaluated_count: Optional[int] = None
|
| 37 |
awaiting_evaluation: Optional[int] = None
|
src/models/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from ._base import Base
|
| 2 |
-
from ._proposal import Proposal, ProposalStatus
|
| 3 |
from ._rfp import RFP
|
| 4 |
from ._proposal_ai_analysis import (
|
| 5 |
ProposalAIAnalysis,
|
|
@@ -10,6 +10,7 @@ from ._proposal_ai_analysis import (
|
|
| 10 |
)
|
| 11 |
from ._proposal_detailed_analysis import ProposalDetailAnalysis, OperationCriteria
|
| 12 |
from ._proposal_query_models import QueryType, CreateOrUpdateType
|
|
|
|
| 13 |
|
| 14 |
__all__ = [
|
| 15 |
"Base",
|
|
@@ -25,6 +26,9 @@ __all__ = [
|
|
| 25 |
"QueryType",
|
| 26 |
"CreateOrUpdateType",
|
| 27 |
"OperationCriteria",
|
|
|
|
|
|
|
|
|
|
| 28 |
]
|
| 29 |
__version__ = "0.1.0"
|
| 30 |
__author__ = "Aryan Jain"
|
|
|
|
| 1 |
from ._base import Base
|
| 2 |
+
from ._proposal import Proposal, ProposalStatus, GateCriteria
|
| 3 |
from ._rfp import RFP
|
| 4 |
from ._proposal_ai_analysis import (
|
| 5 |
ProposalAIAnalysis,
|
|
|
|
| 10 |
)
|
| 11 |
from ._proposal_detailed_analysis import ProposalDetailAnalysis, OperationCriteria
|
| 12 |
from ._proposal_query_models import QueryType, CreateOrUpdateType
|
| 13 |
+
from ._letters import Letter, LetterType
|
| 14 |
|
| 15 |
__all__ = [
|
| 16 |
"Base",
|
|
|
|
| 26 |
"QueryType",
|
| 27 |
"CreateOrUpdateType",
|
| 28 |
"OperationCriteria",
|
| 29 |
+
"GateCriteria",
|
| 30 |
+
"Letter",
|
| 31 |
+
"LetterType",
|
| 32 |
]
|
| 33 |
__version__ = "0.1.0"
|
| 34 |
__author__ = "Aryan Jain"
|
src/models/_letters.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from enum import Enum as PyEnum
|
| 2 |
+
|
| 3 |
+
from sqlalchemy import (
|
| 4 |
+
Column,
|
| 5 |
+
DateTime,
|
| 6 |
+
Enum,
|
| 7 |
+
Float,
|
| 8 |
+
ForeignKey,
|
| 9 |
+
Integer,
|
| 10 |
+
String,
|
| 11 |
+
func,
|
| 12 |
+
)
|
| 13 |
+
from sqlalchemy.dialects.postgresql import UUID
|
| 14 |
+
from pydantic import BaseModel
|
| 15 |
+
from ._base import Base
|
| 16 |
+
|
| 17 |
+
class 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())
|
src/models/_proposal.py
CHANGED
|
@@ -19,6 +19,10 @@ class ProposalStatus(PyEnum):
|
|
| 19 |
EVALUATED = "EVALUATED"
|
| 20 |
IN_REVIEW = "IN_REVIEW"
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
class Proposal(Base):
|
| 24 |
__tablename__ = "proposals"
|
|
@@ -28,6 +32,8 @@ class Proposal(Base):
|
|
| 28 |
UUID(as_uuid=True), ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False
|
| 29 |
)
|
| 30 |
name = Column(String, nullable=False)
|
|
|
|
|
|
|
| 31 |
status = Column(Enum(ProposalStatus), nullable=False)
|
| 32 |
created_at = Column(DateTime, nullable=False, default=func.now())
|
| 33 |
updated_at = Column(
|
|
|
|
| 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"
|
|
|
|
| 32 |
UUID(as_uuid=True), ForeignKey("rfps.id", ondelete="CASCADE"), nullable=False
|
| 33 |
)
|
| 34 |
name = Column(String, nullable=False)
|
| 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(
|
src/models/_rfp.py
CHANGED
|
@@ -19,6 +19,7 @@ class RFP(Base):
|
|
| 19 |
|
| 20 |
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
|
| 21 |
rfp_number = Column(String, nullable=False)
|
|
|
|
| 22 |
received_proposals = Column(Integer, nullable=False)
|
| 23 |
evaluated_count = Column(Integer, nullable=False)
|
| 24 |
awaiting_evaluation = Column(Integer, nullable=False)
|
|
|
|
| 19 |
|
| 20 |
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False)
|
| 21 |
rfp_number = Column(String, nullable=False)
|
| 22 |
+
name = Column(String, nullable=False)
|
| 23 |
received_proposals = Column(Integer, nullable=False)
|
| 24 |
evaluated_count = Column(Integer, nullable=False)
|
| 25 |
awaiting_evaluation = Column(Integer, nullable=False)
|
src/repositories/__init__.py
CHANGED
|
@@ -2,7 +2,14 @@ from ._base_repository import BaseRepository
|
|
| 2 |
from ._rfp_repository import RFPRepository
|
| 3 |
from ._proposal_repository import ProposalRepository
|
| 4 |
from ._proposal_detailed_analysis_repository import ProposalDetailedAnalysisRepository
|
|
|
|
| 5 |
|
| 6 |
-
__all__ = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
__version__ = "0.1.0"
|
| 8 |
-
__author__ = "Aryan Jain"
|
|
|
|
| 2 |
from ._rfp_repository import RFPRepository
|
| 3 |
from ._proposal_repository import ProposalRepository
|
| 4 |
from ._proposal_detailed_analysis_repository import ProposalDetailedAnalysisRepository
|
| 5 |
+
from ._letter_repository import LetterRepository
|
| 6 |
|
| 7 |
+
__all__ = [
|
| 8 |
+
"BaseRepository",
|
| 9 |
+
"RFPRepository",
|
| 10 |
+
"ProposalRepository",
|
| 11 |
+
"ProposalDetailedAnalysisRepository",
|
| 12 |
+
"LetterRepository",
|
| 13 |
+
]
|
| 14 |
__version__ = "0.1.0"
|
| 15 |
+
__author__ = "Aryan Jain"
|
src/repositories/_letter_repository.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uuid
|
| 2 |
+
from sqlalchemy import select
|
| 3 |
+
|
| 4 |
+
from src.models import Letter, LetterType
|
| 5 |
+
|
| 6 |
+
from ._base_repository import BaseRepository
|
| 7 |
+
|
| 8 |
+
class LetterRepository(BaseRepository):
|
| 9 |
+
def __init__(self):
|
| 10 |
+
super().__init__(Letter)
|
| 11 |
+
|
| 12 |
+
async def __aenter__(self):
|
| 13 |
+
return self
|
| 14 |
+
|
| 15 |
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
async def get_letters(self, id: str = None, proposal_id: str = None, letter_type: LetterType = None):
|
| 19 |
+
async with self.get_session() as session:
|
| 20 |
+
query = select(Letter)
|
| 21 |
+
if id:
|
| 22 |
+
query = query.where(Letter.id == id)
|
| 23 |
+
if proposal_id:
|
| 24 |
+
query = query.where(Letter.proposal_id == proposal_id)
|
| 25 |
+
if letter_type:
|
| 26 |
+
query = query.where(Letter.letter_type == letter_type)
|
| 27 |
+
output = await session.execute(query)
|
| 28 |
+
results = output.scalars().all()
|
| 29 |
+
return [{k: v for k, v in result.__dict__.items() if not k.startswith('_')} for result in results]
|
| 30 |
+
|
| 31 |
+
async def create_letter(self, letter: dict):
|
| 32 |
+
async with self.get_session() as session:
|
| 33 |
+
letter_type = letter["letter_type"]
|
| 34 |
+
proposal_id = letter["proposal_id"]
|
| 35 |
+
query = select(Letter).where(Letter.proposal_id == proposal_id, Letter.letter_type == letter_type)
|
| 36 |
+
output = await session.execute(query)
|
| 37 |
+
instances = output.scalars().all()
|
| 38 |
+
if instances:
|
| 39 |
+
return False
|
| 40 |
+
id = str(uuid.uuid4())
|
| 41 |
+
letter["id"] = id
|
| 42 |
+
instance = Letter(**letter)
|
| 43 |
+
session.add(instance)
|
| 44 |
+
await session.commit()
|
| 45 |
+
await session.refresh(instance)
|
| 46 |
+
return [{k: v for k, v in instance.__dict__.items() if not k.startswith('_')}]
|
| 47 |
+
|
| 48 |
+
async def update_letter(self, letter: dict):
|
| 49 |
+
async with self.get_session() as session:
|
| 50 |
+
id = letter["id"]
|
| 51 |
+
query = select(Letter).where(Letter.id == id)
|
| 52 |
+
output = await session.execute(query)
|
| 53 |
+
instance = output.scalars().one()
|
| 54 |
+
proposal_id = instance.proposal_id
|
| 55 |
+
if "letter_type" in letter:
|
| 56 |
+
query = select(Letter).where(Letter.proposal_id == proposal_id, Letter.letter_type == letter["letter_type"])
|
| 57 |
+
output = await session.execute(query)
|
| 58 |
+
instances = output.scalars().all()
|
| 59 |
+
if instances:
|
| 60 |
+
return False
|
| 61 |
+
for key, value in letter.items():
|
| 62 |
+
setattr(instance, key, value)
|
| 63 |
+
await session.commit()
|
| 64 |
+
await session.refresh(instance)
|
| 65 |
+
return [{k: v for k, v in instance.__dict__.items() if not k.startswith('_')}]
|
| 66 |
+
|
| 67 |
+
async def delete_letter(self, id: str = None, proposal_id: str = None):
|
| 68 |
+
async with self.get_session() as session:
|
| 69 |
+
query = select(Letter)
|
| 70 |
+
if id:
|
| 71 |
+
query = query.where(Letter.id == id)
|
| 72 |
+
if proposal_id:
|
| 73 |
+
query = query.where(Letter.proposal_id == proposal_id)
|
| 74 |
+
output = await session.execute(query)
|
| 75 |
+
instances = output.scalars().all()
|
| 76 |
+
for instance in instances:
|
| 77 |
+
await session.delete(instance)
|
| 78 |
+
await session.commit()
|
| 79 |
+
return True
|
src/services/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from ._proposal_ai_analysis_service import (
|
|
| 6 |
CreateOrUpdateType,
|
| 7 |
)
|
| 8 |
from ._proposal_detailed_analysis_service import ProposalDetailedAnalysisService
|
|
|
|
| 9 |
|
| 10 |
__all__ = [
|
| 11 |
"RFPService",
|
|
@@ -14,6 +15,7 @@ __all__ = [
|
|
| 14 |
"QueryType",
|
| 15 |
"CreateOrUpdateType",
|
| 16 |
"ProposalDetailedAnalysisService",
|
|
|
|
| 17 |
]
|
| 18 |
__version__ = "0.1.0"
|
| 19 |
__author__ = "Aryan Jain"
|
|
|
|
| 6 |
CreateOrUpdateType,
|
| 7 |
)
|
| 8 |
from ._proposal_detailed_analysis_service import ProposalDetailedAnalysisService
|
| 9 |
+
from ._letter_service import LetterService
|
| 10 |
|
| 11 |
__all__ = [
|
| 12 |
"RFPService",
|
|
|
|
| 15 |
"QueryType",
|
| 16 |
"CreateOrUpdateType",
|
| 17 |
"ProposalDetailedAnalysisService",
|
| 18 |
+
"LetterService",
|
| 19 |
]
|
| 20 |
__version__ = "0.1.0"
|
| 21 |
__author__ = "Aryan Jain"
|
src/services/_letter_service.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.repositories import LetterRepository
|
| 2 |
+
from src.models import Letter, LetterType
|
| 3 |
+
|
| 4 |
+
class LetterService:
|
| 5 |
+
def __init__(self):
|
| 6 |
+
self.letter_repository = LetterRepository
|
| 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_letters(self, id: str = None, proposal_id: str = None, letter_type: LetterType = None):
|
| 15 |
+
async with self.letter_repository() as repository:
|
| 16 |
+
return await repository.get_letters(id=id, proposal_id=proposal_id, letter_type=letter_type)
|
| 17 |
+
|
| 18 |
+
async def create_letter(self, letter: dict):
|
| 19 |
+
async with self.letter_repository() as repository:
|
| 20 |
+
return await repository.create_letter(letter)
|
| 21 |
+
|
| 22 |
+
async def update_letter(self, letter: dict):
|
| 23 |
+
async with self.letter_repository() as repository:
|
| 24 |
+
return await repository.update_letter(letter)
|
| 25 |
+
|
| 26 |
+
async def delete_letter(self, id: str = None, proposal_id: str = None):
|
| 27 |
+
async with self.letter_repository() as repository:
|
| 28 |
+
return await repository.delete_letter(id)
|