From febbff23cd5ee520224a79443deff1981f2285c4 Mon Sep 17 00:00:00 2001 From: taylorlynn Date: Wed, 21 Jan 2026 14:54:38 -0500 Subject: [PATCH 1/6] Initial analysis changes --- app/models/domain/analysis.py | 2 -- app/services/analysis_orchestrator.py | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/domain/analysis.py b/app/models/domain/analysis.py index b107940..94c775d 100644 --- a/app/models/domain/analysis.py +++ b/app/models/domain/analysis.py @@ -11,10 +11,8 @@ @dataclass class LogProbsData: - anth_conf_score: float tokens: List[str] probs: List[float] - alternatives: List[Dict[str, float]] @dataclass diff --git a/app/services/analysis_orchestrator.py b/app/services/analysis_orchestrator.py index 7ecb9e7..cb571b7 100644 --- a/app/services/analysis_orchestrator.py +++ b/app/services/analysis_orchestrator.py @@ -248,7 +248,8 @@ async def _generate_analysis( logger.info(con_score) current_analysis.confidence_score = float(con_score) # log_data = await self._get_anth_confidence_score(statement=claim_text, veracity_score=veracity_score) - # current_analysis.log_probs = log_data + log_data = LogProbsData(tokens=analysis_text, log_probs=log_probs) + current_analysis.log_probs = log_data updated_analysis = await self._analysis_repo.update(current_analysis) From c3c66bb8dd1e516aa152e04b26731eee053683ef Mon Sep 17 00:00:00 2001 From: taylorlynn Date: Wed, 21 Jan 2026 16:46:43 -0500 Subject: [PATCH 2/6] Save the log probs when running the exerpiment end point --- app/services/analysis_orchestrator.py | 2 +- docker-entrypoint.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/analysis_orchestrator.py b/app/services/analysis_orchestrator.py index cb571b7..1a9e2f3 100644 --- a/app/services/analysis_orchestrator.py +++ b/app/services/analysis_orchestrator.py @@ -248,7 +248,7 @@ async def _generate_analysis( logger.info(con_score) current_analysis.confidence_score = float(con_score) # log_data = await self._get_anth_confidence_score(statement=claim_text, veracity_score=veracity_score) - log_data = LogProbsData(tokens=analysis_text, log_probs=log_probs) + log_data = LogProbsData(tokens=analysis_text, probs=log_probs) current_analysis.log_probs = log_data updated_analysis = await self._analysis_repo.update(current_analysis) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index ad62266..49d87be 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -8,7 +8,7 @@ echo "Waiting for database to be ready..." python scripts/wait_for_db.py echo "Running database migrations..." -alembic upgrade head +# alembic upgrade head echo "Starting the application..." uvicorn app.main:app --host 0.0.0.0 --port 8001 \ No newline at end of file From 7034f6aeabf59ae758ea2bf617fb0665148b7757 Mon Sep 17 00:00:00 2001 From: taylorlynn Date: Wed, 21 Jan 2026 16:53:08 -0500 Subject: [PATCH 3/6] reset the docker entry point --- docker-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 49d87be..ad62266 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -8,7 +8,7 @@ echo "Waiting for database to be ready..." python scripts/wait_for_db.py echo "Running database migrations..." -# alembic upgrade head +alembic upgrade head echo "Starting the application..." uvicorn app.main:app --host 0.0.0.0 --port 8001 \ No newline at end of file From f1cc517f3efef5f69116843357644b5cf7a3da6d Mon Sep 17 00:00:00 2001 From: taylorlynn Date: Wed, 21 Jan 2026 16:56:49 -0500 Subject: [PATCH 4/6] analysis is created --- app/models/domain/analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/domain/analysis.py b/app/models/domain/analysis.py index 94c775d..faa70be 100644 --- a/app/models/domain/analysis.py +++ b/app/models/domain/analysis.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from datetime import datetime -from typing import Optional, List, Dict +from typing import Optional, List from uuid import UUID import pickle From 5bb5839bc7f8ceb069766609294e53b5aa2c2b1b Mon Sep 17 00:00:00 2001 From: taylorlynn Date: Thu, 22 Jan 2026 11:54:55 -0500 Subject: [PATCH 5/6] fixed log probs and added limit --- app/api/endpoints/claim_endpoints.py | 16 +++++++++++++- app/core/exceptions.py | 10 +++++++++ .../implementations/claim_repository.py | 21 ++++++++++++++++++- app/services/claim_service.py | 19 ++++++++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/app/api/endpoints/claim_endpoints.py b/app/api/endpoints/claim_endpoints.py index bc3fe35..9b65598 100644 --- a/app/api/endpoints/claim_endpoints.py +++ b/app/api/endpoints/claim_endpoints.py @@ -25,6 +25,7 @@ from app.services.analysis_orchestrator import AnalysisOrchestrator from app.core.exceptions import NotFoundException, NotAuthorizedException from app.services.interfaces.embedding_generator import EmbeddingGeneratorInterface +from app.core.exceptions import MonthlyLimitExceededError router = APIRouter(prefix="/claims", tags=["claims"]) logger = logging.getLogger(__name__) @@ -45,8 +46,15 @@ async def create_claim( language=data.language, batch_user_id=data.batch_user_id, batch_post_id=data.batch_post_id, + auth0_id=current_user.auth0_id, ) return ClaimRead.model_validate(claim) + except MonthlyLimitExceededError: + # We don't have 'e.limit' anymore, so we just say "Limit reached" + raise HTTPException( + status_code=429, + detail="You have reached your monthly claim limit." + ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create claim: {str(e)}" @@ -65,12 +73,18 @@ async def create_claims_batch( raise HTTPException(status_code=400, detail="Maximum of 100 claims allowed.") try: - created_claims = await claim_service.create_claims_batch(claims, current_user.id) + created_claims = await claim_service.create_claims_batch(claims, current_user.id, auth0_id=current_user.auth0_id,) claim_ids = [str(claim.id) for claim in created_claims] background_tasks.add_task( claim_service.process_claims_batch_async, created_claims, current_user.id, analysis_orchestrator ) return {"message": f"Processing {len(created_claims)} claims in the background.", "claim_ids": claim_ids} + except MonthlyLimitExceededError: + # We don't have 'e.limit' anymore, so we just say "Limit reached" + raise HTTPException( + status_code=429, + detail="You have reached your monthly claim limit." + ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to queue batch: {str(e)}" diff --git a/app/core/exceptions.py b/app/core/exceptions.py index 2f1a033..d939ccf 100644 --- a/app/core/exceptions.py +++ b/app/core/exceptions.py @@ -37,6 +37,16 @@ class DuplicateUserError(Exception): pass +""" +Claim exceptions +""" + + +class MonthlyLimitExceededError(Exception): + """Raised when a user hits their monthly claim limit.""" + + pass + """ Feedback exceptions diff --git a/app/repositories/implementations/claim_repository.py b/app/repositories/implementations/claim_repository.py index 4ce5814..bb535ca 100644 --- a/app/repositories/implementations/claim_repository.py +++ b/app/repositories/implementations/claim_repository.py @@ -3,7 +3,7 @@ from uuid import UUID from sqlalchemy import select, func, and_ from sqlalchemy.ext.asyncio import AsyncSession -from datetime import datetime +from datetime import datetime, UTC from app.models.database.models import ClaimModel, ClaimStatus from app.models.domain.claim import Claim @@ -93,6 +93,25 @@ async def get_claims_in_date_range(self, start_date: datetime, end_date: datetim ) result = await self._session.execute(stmt) return [self._to_domain(claim) for claim in result.scalars().all()] + + async def get_monthly_claim_count(self, user_id: str) -> int: + """Counts how many claims a user has created this month.""" + + # Calculate the 1st of the current month + now = datetime.now(UTC) + start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + + query = ( + select(func.count()) + .select_from(ClaimModel) + .where( + ClaimModel.user_id == user_id, + ClaimModel.created_at >= start_of_month + ) + ) + + result = await self._session.execute(query) + return result.scalar_one() async def insert_many(self, claim: List[Claim]) -> List[Claim]: models = [self._to_model(claim) for claim in claim] diff --git a/app/services/claim_service.py b/app/services/claim_service.py index 21786f1..f2b6451 100644 --- a/app/services/claim_service.py +++ b/app/services/claim_service.py @@ -17,6 +17,7 @@ from app.repositories.implementations.claim_repository import ClaimRepository from app.repositories.implementations.analysis_repository import AnalysisRepository from app.services.analysis_orchestrator import AnalysisOrchestrator +from app.core.exceptions import MonthlyLimitExceededError from app.core.exceptions import NotFoundException, NotAuthorizedException @@ -31,6 +32,8 @@ logger = logging.getLogger(__name__) executor = ThreadPoolExecutor(max_workers=1) +RESTRICTED_CLIENT_ID = "hHRhJr5OoJhWumP87MHk5RldejycVAmC@clients" +MONTHLY_LIMIT = 3000 class ClaimService: def __init__(self, claim_repository: ClaimRepository, analysis_repository: AnalysisRepository): @@ -45,9 +48,17 @@ async def create_claim( language: str, batch_user_id: str = None, batch_post_id: str = None, + auth0_id: str = None, ) -> Claim: """Create a new claim.""" now = datetime.now(UTC) + + if auth0_id is not None: + if auth0_id == RESTRICTED_CLIENT_ID: + current_count = await self._claim_repo.get_monthly_claim_count(user_id) + + if current_count >= MONTHLY_LIMIT: + raise MonthlyLimitExceededError() claim = Claim( id=uuid4(), user_id=user_id, @@ -234,9 +245,15 @@ def _heavy_clustering_math(claims, num_clusters): result = await loop.run_in_executor(executor, _heavy_clustering_math, claims, num_clusters) return result - async def create_claims_batch(self, claims: List[Claim], user_id: str) -> List[Claim]: + async def create_claims_batch(self, claims: List[Claim], user_id: str, auth0_id: str = None,) -> List[Claim]: # Map ClaimCreate + user_id → Claim DB objects now = datetime.now(UTC) + if auth0_id is not None: + if auth0_id == RESTRICTED_CLIENT_ID: + current_count = await self._claim_repo.get_monthly_claim_count(user_id) + + if current_count + len(claims) >= MONTHLY_LIMIT: + raise MonthlyLimitExceededError() claim_models = [ Claim( id=uuid4(), From b698cb1ce6cbfc9ff20e9462b0c7bbe25eb11f52 Mon Sep 17 00:00:00 2001 From: taylorlynn Date: Thu, 22 Jan 2026 11:57:20 -0500 Subject: [PATCH 6/6] all files formatted --- app/api/endpoints/claim_endpoints.py | 16 +++++++--------- app/core/exceptions.py | 1 + .../implementations/claim_repository.py | 11 ++++------- app/services/claim_service.py | 12 +++++++++--- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/api/endpoints/claim_endpoints.py b/app/api/endpoints/claim_endpoints.py index 9b65598..8ecfcb4 100644 --- a/app/api/endpoints/claim_endpoints.py +++ b/app/api/endpoints/claim_endpoints.py @@ -51,10 +51,7 @@ async def create_claim( return ClaimRead.model_validate(claim) except MonthlyLimitExceededError: # We don't have 'e.limit' anymore, so we just say "Limit reached" - raise HTTPException( - status_code=429, - detail="You have reached your monthly claim limit." - ) + raise HTTPException(status_code=429, detail="You have reached your monthly claim limit.") except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create claim: {str(e)}" @@ -73,7 +70,11 @@ async def create_claims_batch( raise HTTPException(status_code=400, detail="Maximum of 100 claims allowed.") try: - created_claims = await claim_service.create_claims_batch(claims, current_user.id, auth0_id=current_user.auth0_id,) + created_claims = await claim_service.create_claims_batch( + claims, + current_user.id, + auth0_id=current_user.auth0_id, + ) claim_ids = [str(claim.id) for claim in created_claims] background_tasks.add_task( claim_service.process_claims_batch_async, created_claims, current_user.id, analysis_orchestrator @@ -81,10 +82,7 @@ async def create_claims_batch( return {"message": f"Processing {len(created_claims)} claims in the background.", "claim_ids": claim_ids} except MonthlyLimitExceededError: # We don't have 'e.limit' anymore, so we just say "Limit reached" - raise HTTPException( - status_code=429, - detail="You have reached your monthly claim limit." - ) + raise HTTPException(status_code=429, detail="You have reached your monthly claim limit.") except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to queue batch: {str(e)}" diff --git a/app/core/exceptions.py b/app/core/exceptions.py index d939ccf..bd5e34a 100644 --- a/app/core/exceptions.py +++ b/app/core/exceptions.py @@ -37,6 +37,7 @@ class DuplicateUserError(Exception): pass + """ Claim exceptions """ diff --git a/app/repositories/implementations/claim_repository.py b/app/repositories/implementations/claim_repository.py index bb535ca..72a300f 100644 --- a/app/repositories/implementations/claim_repository.py +++ b/app/repositories/implementations/claim_repository.py @@ -93,10 +93,10 @@ async def get_claims_in_date_range(self, start_date: datetime, end_date: datetim ) result = await self._session.execute(stmt) return [self._to_domain(claim) for claim in result.scalars().all()] - + async def get_monthly_claim_count(self, user_id: str) -> int: """Counts how many claims a user has created this month.""" - + # Calculate the 1st of the current month now = datetime.now(UTC) start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) @@ -104,12 +104,9 @@ async def get_monthly_claim_count(self, user_id: str) -> int: query = ( select(func.count()) .select_from(ClaimModel) - .where( - ClaimModel.user_id == user_id, - ClaimModel.created_at >= start_of_month - ) + .where(ClaimModel.user_id == user_id, ClaimModel.created_at >= start_of_month) ) - + result = await self._session.execute(query) return result.scalar_one() diff --git a/app/services/claim_service.py b/app/services/claim_service.py index f2b6451..62a8b16 100644 --- a/app/services/claim_service.py +++ b/app/services/claim_service.py @@ -35,6 +35,7 @@ RESTRICTED_CLIENT_ID = "hHRhJr5OoJhWumP87MHk5RldejycVAmC@clients" MONTHLY_LIMIT = 3000 + class ClaimService: def __init__(self, claim_repository: ClaimRepository, analysis_repository: AnalysisRepository): self._claim_repo = claim_repository @@ -56,7 +57,7 @@ async def create_claim( if auth0_id is not None: if auth0_id == RESTRICTED_CLIENT_ID: current_count = await self._claim_repo.get_monthly_claim_count(user_id) - + if current_count >= MONTHLY_LIMIT: raise MonthlyLimitExceededError() claim = Claim( @@ -245,13 +246,18 @@ def _heavy_clustering_math(claims, num_clusters): result = await loop.run_in_executor(executor, _heavy_clustering_math, claims, num_clusters) return result - async def create_claims_batch(self, claims: List[Claim], user_id: str, auth0_id: str = None,) -> List[Claim]: + async def create_claims_batch( + self, + claims: List[Claim], + user_id: str, + auth0_id: str = None, + ) -> List[Claim]: # Map ClaimCreate + user_id → Claim DB objects now = datetime.now(UTC) if auth0_id is not None: if auth0_id == RESTRICTED_CLIENT_ID: current_count = await self._claim_repo.get_monthly_claim_count(user_id) - + if current_count + len(claims) >= MONTHLY_LIMIT: raise MonthlyLimitExceededError() claim_models = [