mirror of
https://github.com/marcogll/telegram_expenses_controller.git
synced 2026-01-13 13:25:15 +00:00
91 lines
4.1 KiB
Python
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
|