fastcheck_api fix

This commit is contained in:
valenti 2026-04-29 11:50:59 -04:00
parent 60732dc8cf
commit 0ac421da93
9 changed files with 27 additions and 313 deletions

View File

@ -38,8 +38,6 @@ async def login(payload: LoginRequest, db: Annotated[AsyncIOMotorDatabase, Depen
raise
except ValueError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
except RuntimeError as e:
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(e))
@router.get(

View File

@ -1,12 +1,9 @@
from __future__ import annotations
import logging
import re
import time
from typing import Annotated
import datetime as dt
from bson import ObjectId
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from motor.motor_asyncio import AsyncIOMotorDatabase
@ -17,16 +14,13 @@ from fastcheck_api.app.schemas.checks import (
EvaluationJobOut,
ListJobsResponse,
ListResultsResponse,
LookupResponse,
)
from fastcheck_api.app.services.audit_service import AuditService
from fastcheck_api.app.services.check_service import CheckService, InsufficientCreditsError
from fastcheck_api.app.services.legacy_service import legacy_service
from fastcheck_api.app.utils.mongo import jsonable, sanitize_rut
from fastcheck_api.app.utils.mongo import sanitize_rut
router = APIRouter(prefix="/checks", tags=["checks"])
logger = logging.getLogger("fastcheck.checks")
_MASK_REPLACEMENTS: dict[str, str] = {
"sheriff": "provider_a",
@ -132,176 +126,6 @@ async def create_check(
)
@router.post(
"/lookup",
response_model=LookupResponse,
status_code=status.HTTP_201_CREATED,
summary="Lookup a RUT (sync)",
description="Create a check, run the legacy lookup immediately, persist the result, and return job+result in a single request.",
)
async def lookup(
request: Request,
payload: CreateCheckRequest,
user: Annotated[CurrentUser, Depends(require_permission("evaluation:create"))],
db: Annotated[AsyncIOMotorDatabase, Depends(get_db)],
):
start = time.perf_counter()
clean_rut = sanitize_rut(payload.supplier.rut)
token = legacy_service.get_user_token(user_id=user.id)
if not token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Legacy token missing or expired; login again",
)
try:
job = await CheckService.create_single_check(
db=db,
tenant_id=user.tenant,
user_id=user.id,
rut=clean_rut,
name=payload.supplier.name,
enqueue=False,
)
await AuditService.log_consulta(
db=db,
tenant_id=user.tenant,
user_id=user.id,
consulta_type="individual",
rut=clean_rut,
endpoint="/api/v1/checks/lookup",
response_status=status.HTTP_201_CREATED,
request_data={"supplier": {"rut": clean_rut, "name": payload.supplier.name}},
processing_time_ms=int((time.perf_counter() - start) * 1000),
credits_used=1,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
metadata={"evaluationType": "single", "mode": "sync"},
)
except InsufficientCreditsError:
await AuditService.log_consulta(
db=db,
tenant_id=user.tenant,
user_id=user.id,
consulta_type="individual",
rut=clean_rut,
endpoint="/api/v1/checks/lookup",
response_status=status.HTTP_402_PAYMENT_REQUIRED,
request_data={"supplier": {"rut": clean_rut, "name": payload.supplier.name}},
processing_time_ms=int((time.perf_counter() - start) * 1000),
credits_used=0,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
metadata={"reason": "insufficient_credits", "mode": "sync"},
)
raise HTTPException(status_code=status.HTTP_402_PAYMENT_REQUIRED, detail="Insufficient credits")
job_id = str(job.get("_id") or "")
try:
job_oid = ObjectId(job_id)
except Exception:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Invalid job id")
now = dt.datetime.now(dt.timezone.utc)
try:
result_data = await legacy_service.lookup_rut(rut=clean_rut, token=token)
ai_summary = result_data.get("aiAnalysis") if isinstance(result_data, dict) else None
if ai_summary and isinstance(ai_summary, str) and ai_summary.strip():
try:
await legacy_service.save_fastcheck_summary(rut=clean_rut, summary=ai_summary, token=token)
except Exception:
pass
insert = await db["evaluationresults"].insert_one(
{
"jobId": job_oid,
"tenantId": user.tenant,
"rut": clean_rut,
"name": payload.supplier.name,
"status": "success",
"data": result_data,
"createdAt": now,
"updatedAt": now,
}
)
await db["evaluationjobs"].update_one(
{"_id": job_oid, "tenantId": user.tenant},
{
"$set": {
"completedEvaluations": 1,
"failedEvaluations": 0,
"status": "completed",
"updatedAt": now,
}
},
)
stored_job = await db["evaluationjobs"].find_one({"_id": job_oid, "tenantId": user.tenant})
stored_result = await db["evaluationresults"].find_one({"_id": insert.inserted_id})
response_payload = {
"job": jsonable(stored_job) if stored_job else job,
"result": jsonable(stored_result)
if stored_result
else jsonable(
{
"jobId": str(job_oid),
"tenantId": user.tenant,
"rut": clean_rut,
"name": payload.supplier.name,
"status": "success",
"data": result_data,
"createdAt": now,
"updatedAt": now,
}
),
}
return _mask_json(response_payload)
except Exception as e:
request_id = getattr(request.state, "request_id", None)
logger.exception(
"lookup_failed",
extra={
"request_id": request_id,
"tenant_id": user.tenant,
"user_id": user.id,
"rut": clean_rut,
},
)
reason = str(e).replace("\n", " ").strip()
if not reason:
reason = e.__class__.__name__
if len(reason) > 500:
reason = reason[:500] + "..."
await db["evaluationresults"].insert_one(
{
"jobId": job_oid,
"tenantId": user.tenant,
"rut": clean_rut,
"name": payload.supplier.name,
"status": "failed",
"error": reason,
"createdAt": now,
"updatedAt": now,
}
)
await db["evaluationjobs"].update_one(
{"_id": job_oid, "tenantId": user.tenant},
{
"$set": {
"completedEvaluations": 0,
"failedEvaluations": 1,
"status": "failed",
"updatedAt": now,
}
},
)
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=reason)
@router.get(
"/{job_id}",
response_model=EvaluationJobOut,

View File

@ -55,8 +55,3 @@ class ListResultsResponse(BaseModel):
results: list[EvaluationResultOut]
total: int = Field(description="Total results in this job.", examples=[1])
pages: int = Field(description="Total pages for the current limit.", examples=[1])
class LookupResponse(BaseModel):
job: EvaluationJobOut = Field(description="Created evaluation job.")
result: EvaluationResultOut = Field(description="Immediate evaluation result saved for this job.")

View File

@ -4,7 +4,6 @@ from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorDatabase
from fastcheck_api.app.core.security import create_access_token, verify_password
from fastcheck_api.app.services.legacy_service import legacy_service
class AuthService:
@ -37,8 +36,6 @@ class AuthService:
tenant_id=tenant_id_str,
)
await legacy_service.login_user(user_id=str(user["_id"]), email=email.lower(), password=password)
return {
"token": token,
"user": {

View File

@ -24,7 +24,6 @@ class CheckService:
user_id: str,
rut: str,
name: str | None,
enqueue: bool = True,
) -> dict[str, Any]:
now = dt.datetime.now(dt.timezone.utc)
tenant_oid = to_object_id(tenant_id)
@ -77,7 +76,6 @@ class CheckService:
insert = await db["evaluationjobs"].insert_one(job_doc)
job_id = str(insert.inserted_id)
if enqueue:
await rabbitmq_service.publish_evaluation(
{"rut": rut, "name": name, "jobId": job_id, "tenantId": tenant_id, "userId": user_id}
)

View File

@ -1,8 +1,5 @@
from __future__ import annotations
import base64
import json
import time
from typing import Any
import httpx
@ -16,63 +13,11 @@ class LegacyService:
base_url=settings.legacy_base_url,
timeout=settings.legacy_http_timeout_seconds,
)
self._user_tokens: dict[str, tuple[str, float | None]] = {}
async def close(self) -> None:
await self._client.aclose()
def _extract_exp(self, token: str) -> float | None:
try:
parts = token.split(".")
if len(parts) < 2:
return None
payload_b64 = parts[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
payload_raw = base64.urlsafe_b64decode(payload_b64.encode("utf-8"))
payload = json.loads(payload_raw.decode("utf-8"))
exp = payload.get("exp")
if isinstance(exp, (int, float)):
return float(exp)
except Exception:
return None
return None
def set_user_token(self, *, user_id: str, token: str) -> None:
exp = self._extract_exp(token)
self._user_tokens[user_id] = (token, exp)
def get_user_token(self, *, user_id: str) -> str | None:
entry = self._user_tokens.get(user_id)
if not entry:
return None
token, exp = entry
if exp is None:
return token
if (time.time() + 60) < exp:
return token
self._user_tokens.pop(user_id, None)
return None
async def login_user(self, *, user_id: str, email: str, password: str) -> str:
try:
resp = await self._client.post("auth/login", json={"email": email, "password": password})
resp.raise_for_status()
data = resp.json()
token = data.get("token") if isinstance(data, dict) else None
if not token or not isinstance(token, str):
raise RuntimeError("Legacy login did not return a token")
self.set_user_token(user_id=user_id, token=token)
return token
except httpx.HTTPStatusError as e:
body = (e.response.text or "").replace("\n", " ").strip()
if len(body) > 500:
body = body[:500] + "..."
raise RuntimeError(f"Legacy login failed ({e.response.status_code}): {body or 'no body'}") from e
except httpx.RequestError as e:
raise RuntimeError(f"Legacy login request error: {str(e)}") from e
async def lookup_rut(self, *, rut: str, token: str) -> dict[str, Any]:
try:
resp = await self._client.post(
"rut/lookup",
headers={"Authorization": f"Bearer {token}"},
@ -83,16 +28,8 @@ class LegacyService:
if not isinstance(data, dict):
raise RuntimeError("Unexpected legacy response")
return data
except httpx.HTTPStatusError as e:
body = (e.response.text or "").replace("\n", " ").strip()
if len(body) > 500:
body = body[:500] + "..."
raise RuntimeError(f"Legacy lookup failed ({e.response.status_code}): {body or 'no body'}") from e
except httpx.RequestError as e:
raise RuntimeError(f"Legacy lookup request error: {str(e)}") from e
async def save_fastcheck_summary(self, *, rut: str, summary: str, token: str) -> dict[str, Any]:
try:
resp = await self._client.post(
f"rut/fast-check-summary/{rut}",
headers={"Authorization": f"Bearer {token}"},
@ -103,13 +40,6 @@ class LegacyService:
if not isinstance(data, dict):
raise RuntimeError("Unexpected legacy response")
return data
except httpx.HTTPStatusError as e:
body = (e.response.text or "").replace("\n", " ").strip()
if len(body) > 500:
body = body[:500] + "..."
raise RuntimeError(f"Legacy save summary failed ({e.response.status_code}): {body or 'no body'}") from e
except httpx.RequestError as e:
raise RuntimeError(f"Legacy save summary request error: {str(e)}") from e
legacy_service = LegacyService()

View File

@ -18,10 +18,10 @@ from typing import Dict, Any, Optional
from datetime import datetime, timedelta
import requests
from fastapi import FastAPI, HTTPException, Header, Request
from fastapi import FastAPI, HTTPException, Header
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel, ValidationError
from pydantic import BaseModel
from dotenv import load_dotenv
# Configure logging
@ -530,25 +530,7 @@ async def get_credit_score(rut: str):
return execute_sheriff_get(path)
@app.post("/queryRut")
async def query_rut(request: Request):
raw = await request.body()
if not raw:
raise HTTPException(status_code=400, detail="Missing JSON body")
try:
parsed = json.loads(raw.decode("utf-8"))
except Exception:
snippet = raw[:500].decode("utf-8", errors="replace")
raise HTTPException(status_code=400, detail=f"Invalid JSON body: {snippet}")
if not isinstance(parsed, dict):
raise HTTPException(status_code=400, detail="Body must be a JSON object")
try:
payload = QueryRutRequest(**parsed)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())
async def query_rut(payload: QueryRutRequest):
cargar_body = {
"rut": payload.rut.strip(),
"isMonitoring": False,

View File

@ -228,8 +228,6 @@ export class SheriffService {
if (loadRutData == null) {
console.error('loadRutData failed is null');
await logEntry.save();
return logEntry.toObject();
}
//must wait at least 30 seconds before starting the next
await new Promise(resolve => setTimeout(resolve, parseInt(process.env.SHERIFF_API_DELAY || '30000')));
@ -237,12 +235,6 @@ export class SheriffService {
const summaryData = await makeApiCall('summaryData', 'get', `/helper/${sanitizedRut}/summary?complete=true`); //added complete true
console.log(`[DEBUG] After summaryData call, sanitizedRut is still: ${sanitizedRut}`);
if (summaryData == null) {
console.error('summaryData failed is null');
await logEntry.save();
return logEntry.toObject();
}
let societaryMeshExternal: any[] | null = null;
if (summaryData.data.societaryMeshExternal && summaryData.data.societaryMeshExternal.length > 0) {

View File

@ -227,8 +227,6 @@ export class sheriffV2Service {
//console.log(`[DEBUG] loadRutData response RUT: ${loadRutData?.data?.rut || 'No RUT in response'}`);
if (summaryData == null) {
console.error('loadRutData failed is null');
await logEntry.save();
return logEntry.toObject();
}
if(!summaryData.data) {
summaryData.data = {};