fastcheck_api fix
This commit is contained in:
parent
60732dc8cf
commit
0ac421da93
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = {};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user