feat: Update default models for extraction and normalization to GPT-5.6 Luna across configuration files and documentation. Remove deprecated code and improve pricing details in the UI components. Add tests to ensure correct model settings and pricing calculations.

This commit is contained in:
erik
2026-08-16 15:39:01 -05:00
parent 046f26c20c
commit 3b01456875
38 changed files with 615 additions and 639 deletions
@@ -0,0 +1,228 @@
"""Regresiones de persistencia para ingestas desktop síncronas."""
from __future__ import annotations
import asyncio
from contextlib import contextmanager
from types import SimpleNamespace
import pytest
from api import desktop_ingest
from api import main as extraction_api
class FakeSession:
def __init__(self) -> None:
self.added: list[object] = []
self.batch = SimpleNamespace(status="running", updated_at=None)
def add(self, value: object) -> None:
self.added.append(value)
def flush(self) -> None:
pass
def get(self, _model: object, _identifier: str) -> object:
return self.batch
def refresh(self, _value: object) -> None:
pass
def test_multiple_document_types_complete_once_per_radicado(monkeypatch) -> None:
monkeypatch.setattr(
desktop_ingest,
"flatten_extraction_result",
lambda fused, _fields, _aliases: [fused],
)
session = FakeSession()
request = {
"structure_mode": "radicados",
"fields": ["factura/numero", "soporte/codigo"],
"doc_types": [{"name": "factura"}, {"name": "soporte"}],
"documents": [
{"radicado": "RAD-1", "filename": "factura.pdf", "doc_type": "factura"},
{"radicado": "RAD-1", "filename": "soporte.pdf", "doc_type": "soporte"},
],
}
task = SimpleNamespace(
result={"processed": 0, "total": 1, "row_count": 0, "partials": {}},
status="running",
finished_at=None,
updated_at=None,
)
first = desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="factura.pdf",
doc_type="factura",
request=request,
normalized_obj={"numero": "F-1"},
)
desktop_ingest.mark_task_completed_if_done(session, task, "batch-1")
assert first["finalized"] is False
assert task.result["processed"] == 0
assert task.status == "running"
assert task.result["partials"] == {"RAD-1": {"factura": {"numero": "F-1"}}}
assert session.added == []
second = desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="soporte.pdf",
doc_type="soporte",
request=request,
normalized_obj={"codigo": "S-1"},
)
desktop_ingest.mark_task_completed_if_done(session, task, "batch-1")
assert second["finalized"] is True
assert task.result["processed"] == 1
assert task.result["partials"] == {}
assert task.status == "completed"
assert session.batch.status == "completed"
assert len(session.added) == 1
row = session.added[0]
assert row.radicado == "RAD-1"
assert row.raw_json["factura"] == {"numero": "F-1"}
assert row.raw_json["soporte"] == {"codigo": "S-1"}
def test_document_failure_completes_radicado_and_discards_partials(monkeypatch) -> None:
monkeypatch.setattr(
desktop_ingest,
"flatten_extraction_result",
lambda fused, _fields, _aliases: [fused],
)
session = FakeSession()
request = {
"structure_mode": "radicados",
"fields": ["factura/numero", "soporte/codigo"],
"doc_types": [{"name": "factura"}, {"name": "soporte"}],
"documents": [
{"radicado": "RAD-1", "filename": "factura.pdf", "doc_type": "factura"},
{"radicado": "RAD-1", "filename": "soporte.pdf", "doc_type": "soporte"},
],
}
task = SimpleNamespace(
result={"processed": 0, "total": 1, "row_count": 0, "partials": {}},
status="running",
finished_at=None,
updated_at=None,
)
desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="factura.pdf",
doc_type="factura",
request=request,
normalized_obj={"numero": "F-1"},
)
failure = desktop_ingest.record_desktop_ingest_error(
session,
task=task,
radicado="RAD-1",
filename="soporte.pdf",
doc_type="soporte",
error=RuntimeError("backend no disponible"),
)
desktop_ingest.mark_task_completed_if_done(session, task, "batch-1")
assert failure["first_failure"] is True
assert task.result["processed"] == 1
assert task.result["partials"] == {}
assert task.result["failed_radicados"] == ["RAD-1"]
assert task.result["errors"] == [{
"radicado": "RAD-1",
"filename": "soporte.pdf",
"doc_type": "soporte",
"error": "backend no disponible",
}]
assert task.status == "completed"
assert session.added == []
late_success = desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="soporte.pdf",
doc_type="soporte",
request=request,
normalized_obj={"codigo": "S-1"},
)
duplicate_failure = desktop_ingest.record_desktop_ingest_error(
session,
task=task,
radicado="RAD-1",
filename="factura.pdf",
doc_type="factura",
error=RuntimeError("otro fallo tardío"),
)
assert late_success["skipped"] is True
assert duplicate_failure["first_failure"] is False
assert task.result["processed"] == 1
assert task.result["partials"] == {}
assert session.added == []
def test_process_sync_persists_failure_before_reraising(monkeypatch) -> None:
task = SimpleNamespace(
result={"processed": 0, "total": 1, "row_count": 0, "partials": {}},
status="running",
finished_at=None,
updated_at=None,
)
session = FakeSession()
def get(model: object, _identifier: str, **_kwargs: object) -> object:
if model is extraction_api.Task:
return task
return session.batch
session.get = get # type: ignore[method-assign]
@contextmanager
def fake_session_scope():
yield session
class FailingService:
extractor = SimpleNamespace(model="test-model")
def extract_full_from_pdf(self, *_args: object, **_kwargs: object) -> object:
raise RuntimeError("fallo simulado")
monkeypatch.setattr(extraction_api, "session_scope", fake_session_scope)
monkeypatch.setattr(extraction_api, "processing_service", FailingService())
with pytest.raises(RuntimeError, match="fallo simulado"):
asyncio.run(extraction_api._process_sync(
"task-1",
"batch-1",
b"pdf",
"factura.pdf",
"RAD-1",
"factura",
{
"fields": ["factura/numero"],
"doc_types": [{"name": "factura"}],
"documents": [
{"radicado": "RAD-1", "filename": "factura.pdf", "doc_type": "factura"},
],
},
))
assert task.result["processed"] == 1
assert task.result["failed_radicados"] == ["RAD-1"]
assert task.result["errors"][0]["error"] == "fallo simulado"
assert task.status == "completed"