Spaces:
Running
Running
[NOTICKET] Feat: score card
Browse files- externals/databases/pg_crud.py +31 -2
- externals/storages/azure_blob.py +0 -2
- interfaces/api/file.py +39 -1
- services/agentic/profile_scoring.py +1 -1
- services/knowledge/get_profile.py +1 -5
- services/knowledge/knowledge_setup.py +2 -0
- services/knowledge/score_card.py +37 -0
- services/knowledge/upload_file.py +0 -2
externals/databases/pg_crud.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from typing import Optional, List
|
| 2 |
from uuid import UUID
|
| 3 |
|
| 4 |
-
from sqlalchemy import select, and_, update, delete, or_
|
| 5 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 6 |
from externals.databases.pg_models import (
|
| 7 |
CVUser,
|
|
@@ -200,6 +200,30 @@ async def get_file_by_filename(
|
|
| 200 |
return result.scalar_one_or_none()
|
| 201 |
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
async def mark_file_extracted(db: AsyncSession, file_id: UUID):
|
| 204 |
await db.execute(
|
| 205 |
update(CVFile)
|
|
@@ -262,8 +286,13 @@ async def create_profile(
|
|
| 262 |
async def get_profile_by_filename(
|
| 263 |
db: AsyncSession,
|
| 264 |
filename: str,
|
|
|
|
| 265 |
) -> Optional[CVProfile]:
|
| 266 |
-
stmt =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
result = await db.execute(stmt)
|
| 268 |
return result.scalar_one_or_none()
|
| 269 |
|
|
|
|
| 1 |
from typing import Optional, List
|
| 2 |
from uuid import UUID
|
| 3 |
|
| 4 |
+
from sqlalchemy import select, and_, update, delete, or_, func
|
| 5 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 6 |
from externals.databases.pg_models import (
|
| 7 |
CVUser,
|
|
|
|
| 200 |
return result.scalar_one_or_none()
|
| 201 |
|
| 202 |
|
| 203 |
+
async def count_files_by_user(
|
| 204 |
+
db: AsyncSession,
|
| 205 |
+
user_id: str,
|
| 206 |
+
) -> int:
|
| 207 |
+
"""Return number of CVFile rows belonging to a user."""
|
| 208 |
+
result = await db.execute(
|
| 209 |
+
select(func.count(CVFile.file_id)).where(CVFile.user_id == user_id)
|
| 210 |
+
)
|
| 211 |
+
return int(result.scalar_one())
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
async def count_profiles_by_user(
|
| 215 |
+
db: AsyncSession,
|
| 216 |
+
user_id: str,
|
| 217 |
+
) -> int:
|
| 218 |
+
"""Return number of CVProfile rows that are associated with files of a user."""
|
| 219 |
+
result = await db.execute(
|
| 220 |
+
select(func.count(CVProfile.profile_id))
|
| 221 |
+
.join(CVFile, CVProfile.file_id == CVFile.file_id)
|
| 222 |
+
.where(CVFile.user_id == user_id)
|
| 223 |
+
)
|
| 224 |
+
return int(result.scalar_one())
|
| 225 |
+
|
| 226 |
+
|
| 227 |
async def mark_file_extracted(db: AsyncSession, file_id: UUID):
|
| 228 |
await db.execute(
|
| 229 |
update(CVFile)
|
|
|
|
| 286 |
async def get_profile_by_filename(
|
| 287 |
db: AsyncSession,
|
| 288 |
filename: str,
|
| 289 |
+
current_user: CVUser,
|
| 290 |
) -> Optional[CVProfile]:
|
| 291 |
+
stmt = (
|
| 292 |
+
select(CVProfile)
|
| 293 |
+
.join(CVFile, CVProfile.file_id == CVFile.file_id)
|
| 294 |
+
.where(CVProfile.filename == filename, CVFile.user_id == current_user.user_id)
|
| 295 |
+
)
|
| 296 |
result = await db.execute(stmt)
|
| 297 |
return result.scalar_one_or_none()
|
| 298 |
|
externals/storages/azure_blob.py
CHANGED
|
@@ -50,8 +50,6 @@ async def get_container_client() -> ContainerClient:
|
|
| 50 |
)
|
| 51 |
return container_client
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
# async def get_container_client() -> ContainerClient:
|
| 56 |
# service = await get_blob_service_client()
|
| 57 |
# container = service.get_container_client(
|
|
|
|
| 50 |
)
|
| 51 |
return container_client
|
| 52 |
|
|
|
|
|
|
|
| 53 |
# async def get_container_client() -> ContainerClient:
|
| 54 |
# service = await get_blob_service_client()
|
| 55 |
# container = service.get_container_client(
|
interfaces/api/file.py
CHANGED
|
@@ -51,7 +51,7 @@ async def upload_knowledge_many(
|
|
| 51 |
}
|
| 52 |
|
| 53 |
@router.get(
|
| 54 |
-
"/{user_id}",
|
| 55 |
status_code=status.HTTP_200_OK,
|
| 56 |
summary="Get file metadata by user ID",
|
| 57 |
)
|
|
@@ -105,3 +105,41 @@ async def delete_knowledge_file(
|
|
| 105 |
"deleted_by": current_user.email,
|
| 106 |
}
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
|
| 53 |
@router.get(
|
| 54 |
+
"/user/{user_id}",
|
| 55 |
status_code=status.HTTP_200_OK,
|
| 56 |
summary="Get file metadata by user ID",
|
| 57 |
)
|
|
|
|
| 105 |
"deleted_by": current_user.email,
|
| 106 |
}
|
| 107 |
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@router.get(
|
| 111 |
+
"/score_card",
|
| 112 |
+
status_code=status.HTTP_200_OK,
|
| 113 |
+
summary="Get score card data for dashboard summary",
|
| 114 |
+
)
|
| 115 |
+
async def get_score_card(
|
| 116 |
+
db=Depends(get_db),
|
| 117 |
+
current_user=Depends(get_current_user),
|
| 118 |
+
):
|
| 119 |
+
"""
|
| 120 |
+
Retrieve all file metadata uploaded by a specific user.
|
| 121 |
+
|
| 122 |
+
Return:
|
| 123 |
+
- total file
|
| 124 |
+
- total profile extracted
|
| 125 |
+
- percent profile extracted
|
| 126 |
+
"""
|
| 127 |
+
try:
|
| 128 |
+
knowledge_service = KnowledgeService(
|
| 129 |
+
db=db,
|
| 130 |
+
user=current_user,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
data = await knowledge_service.score_card.get_score_card()
|
| 134 |
+
|
| 135 |
+
return {
|
| 136 |
+
"status": "success",
|
| 137 |
+
"message": "Score card retrieved successfully",
|
| 138 |
+
"data": data,
|
| 139 |
+
}
|
| 140 |
+
except Exception as E:
|
| 141 |
+
logger.error(f"get score card error: {E}")
|
| 142 |
+
raise HTTPException(
|
| 143 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 144 |
+
detail=f"get score card error: {E}"
|
| 145 |
+
)
|
services/agentic/profile_scoring.py
CHANGED
|
@@ -491,7 +491,7 @@ class AgenticScoringService:
|
|
| 491 |
# Get profile data all
|
| 492 |
all_profiles = await get_profiles(self.db)
|
| 493 |
print(f"Found {len(all_profiles)} profiles to be scored")
|
| 494 |
-
|
| 495 |
|
| 496 |
_weight:CVWeight = await self.weight_service.get_weight_by_weight_id(weight_id=weight_id)
|
| 497 |
print(f"Found weight: {_weight}")
|
|
|
|
| 491 |
# Get profile data all
|
| 492 |
all_profiles = await get_profiles(self.db)
|
| 493 |
print(f"Found {len(all_profiles)} profiles to be scored")
|
| 494 |
+
|
| 495 |
|
| 496 |
_weight:CVWeight = await self.weight_service.get_weight_by_weight_id(weight_id=weight_id)
|
| 497 |
print(f"Found weight: {_weight}")
|
services/knowledge/get_profile.py
CHANGED
|
@@ -21,14 +21,10 @@ class KnowledgeGetProfileService:
|
|
| 21 |
|
| 22 |
logger.info(f"π Fetching profile: {filename}")
|
| 23 |
|
| 24 |
-
profile = await get_profile_by_filename(self.db, filename)
|
| 25 |
if not profile:
|
| 26 |
return None
|
| 27 |
|
| 28 |
-
# π Tenant isolation
|
| 29 |
-
if profile.tenant_id != self.user.tenant_id:
|
| 30 |
-
raise PermissionError("Access denied")
|
| 31 |
-
|
| 32 |
return profile
|
| 33 |
|
| 34 |
|
|
|
|
| 21 |
|
| 22 |
logger.info(f"π Fetching profile: {filename}")
|
| 23 |
|
| 24 |
+
profile = await get_profile_by_filename(self.db, filename, current_user=self.user)
|
| 25 |
if not profile:
|
| 26 |
return None
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
return profile
|
| 29 |
|
| 30 |
|
services/knowledge/knowledge_setup.py
CHANGED
|
@@ -16,6 +16,7 @@ from services.knowledge.upload_file import KnowledgeFileService
|
|
| 16 |
from services.knowledge.get_profile import KnowledgeGetProfileService
|
| 17 |
from services.knowledge.delete_file import KnowledgeDeleteService
|
| 18 |
from services.knowledge.extract_profile import KnowledgeExtractService
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
class KnowledgeService:
|
|
@@ -31,3 +32,4 @@ class KnowledgeService:
|
|
| 31 |
self.profile = KnowledgeGetProfileService(db, user)
|
| 32 |
self.delete = KnowledgeDeleteService(db, user)
|
| 33 |
self.extract = KnowledgeExtractService(db, user)
|
|
|
|
|
|
| 16 |
from services.knowledge.get_profile import KnowledgeGetProfileService
|
| 17 |
from services.knowledge.delete_file import KnowledgeDeleteService
|
| 18 |
from services.knowledge.extract_profile import KnowledgeExtractService
|
| 19 |
+
from services.knowledge.score_card import KnowledgeScoreCardService
|
| 20 |
|
| 21 |
|
| 22 |
class KnowledgeService:
|
|
|
|
| 32 |
self.profile = KnowledgeGetProfileService(db, user)
|
| 33 |
self.delete = KnowledgeDeleteService(db, user)
|
| 34 |
self.extract = KnowledgeExtractService(db, user)
|
| 35 |
+
self.score_card = KnowledgeScoreCardService(db, user)
|
services/knowledge/score_card.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from externals.databases.pg_crud import (
|
| 2 |
+
count_files_by_user,
|
| 3 |
+
count_profiles_by_user,
|
| 4 |
+
)
|
| 5 |
+
from externals.databases.pg_models import CVUser
|
| 6 |
+
from sqlalchemy.ext.asyncio import AsyncSession
|
| 7 |
+
from utils.logger import get_logger
|
| 8 |
+
|
| 9 |
+
logger = get_logger("score card")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class KnowledgeScoreCardService:
|
| 13 |
+
|
| 14 |
+
def __init__(self, db: AsyncSession, user: CVUser):
|
| 15 |
+
self.db = db
|
| 16 |
+
self.user = user
|
| 17 |
+
|
| 18 |
+
async def get_score_card(self) -> dict:
|
| 19 |
+
"""
|
| 20 |
+
Retrieve extracted profile securely
|
| 21 |
+
"""
|
| 22 |
+
# Count total files for this user (from cv_file table)
|
| 23 |
+
total_file = await count_files_by_user(self.db, self.user.user_id)
|
| 24 |
+
|
| 25 |
+
# Count total extracted profiles associated with this user's files
|
| 26 |
+
total_extracted = await count_profiles_by_user(self.db, self.user.user_id)
|
| 27 |
+
|
| 28 |
+
# percent as 0-1, safely handle division by zero
|
| 29 |
+
percent_extracted = round(total_extracted / total_file, 2) if total_file else 0.0
|
| 30 |
+
|
| 31 |
+
score_card = {
|
| 32 |
+
"total_file": total_file,
|
| 33 |
+
"total_extracted": total_extracted,
|
| 34 |
+
"percent_extracted": percent_extracted*100,
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
return score_card
|
services/knowledge/upload_file.py
CHANGED
|
@@ -90,8 +90,6 @@ class KnowledgeFileService:
|
|
| 90 |
if existing:
|
| 91 |
return existing
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
|
| 96 |
async def get_files_by_user(self, user_id: str):
|
| 97 |
"""
|
|
|
|
| 90 |
if existing:
|
| 91 |
return existing
|
| 92 |
|
|
|
|
|
|
|
| 93 |
|
| 94 |
async def get_files_by_user(self, user_id: str):
|
| 95 |
"""
|