Files

91 lines
4.1 KiB
Python

"""
Enrutador principal de la aplicación.
Orquesta todo el flujo de trabajo de procesamiento de gastos, desde la entrada hasta la persistencia.
"""
import logging
from app.schema.base import RawInput, ProvisionalExpense, FinalExpense, ExpenseStatus
from app.ingestion import text, image, audio, document
from app.ai import extractor, classifier
from app.preprocessing import matcher
from app.persistence import repositories
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
def process_expense_input(db: Session, raw_input: RawInput) -> FinalExpense:
"""
Pipeline completo para procesar una entrada sin procesar.
1. Ingestión: Convertir la entrada (texto, imagen, etc.) en texto sin procesar.
2. Extracción por IA: Analizar el texto sin procesar en datos estructurados.
3. Clasificación/Auditoría por IA: Validar y categorizar el gasto.
4. Persistencia: Guardar el gasto final confirmado en la base de datos.
"""
logger.info(f"El enrutador está procesando la entrada para el usuario {raw_input.user_id} de tipo {raw_input.input_type}")
# 1. Ingestión
raw_text = ""
if raw_input.input_type == "text":
raw_text = text.process_text_input(raw_input.data)
elif raw_input.input_type == "image":
# En una aplicación real, los datos serían bytes, no una ruta de cadena
raw_text = image.process_image_input(raw_input.data.encode())
elif raw_input.input_type == "audio":
raw_text = audio.process_audio_input(raw_input.data.encode())
elif raw_input.input_type == "document":
raw_text = document.process_document_input(raw_input.data.encode())
else:
raise ValueError(f"Tipo de entrada no soportado: {raw_input.input_type}")
if not raw_text:
logger.error("La fase de ingestión resultó en un texto vacío. Abortando.")
# Podríamos querer devolver un estado específico aquí
return None
# 2. Extracción por IA
extracted_data = extractor.extract_expense_data(raw_text)
if not extracted_data.amount or not extracted_data.description:
logger.error("La extracción por IA no pudo encontrar detalles clave. Abortando.")
return None
# 3. Clasificación y Confirmación por IA (simplificado)
# En un bot real, presentarías esto al usuario para su confirmación.
provisional_expense = ProvisionalExpense(
user_id=raw_input.user_id,
extracted_data=extracted_data,
confidence_score=0.0 # Será establecido por el clasificador
)
audited_expense = classifier.classify_and_audit(provisional_expense)
# 3.5 Coincidencia Determinística (Fase 3)
# Enriquecer los datos con categorías de proveedores/palabras clave si están disponibles
match_metadata = matcher.get_metadata_from_match(extracted_data.description)
# Por ahora, auto-confirmamos si la confianza es alta.
if audited_expense.confidence_score > 0.7:
final_expense = FinalExpense(
user_id=audited_expense.user_id,
provider_name=match_metadata.get("matched_name") or audited_expense.extracted_data.description,
amount=audited_expense.extracted_data.amount,
currency=audited_expense.extracted_data.currency,
expense_date=audited_expense.extracted_data.expense_date,
description=audited_expense.extracted_data.description,
category=match_metadata.get("category") or audited_expense.category,
expense_type=match_metadata.get("expense_type") or "personal",
initial_processing_method=match_metadata.get("match_type") or audited_expense.processing_method,
confirmed_by="auto-confirm"
)
# 4. Persistencia
db_record = repositories.save_final_expense(db, final_expense)
logger.info(f"Gasto procesado y guardado con éxito ID {db_record.id}")
return db_record
else:
logger.warning(f"El gasto para el usuario {raw_input.user_id} tiene baja confianza. Esperando confirmación manual.")
# Aquí guardarías el gasto provisional y notificarías al usuario
return None