chore: Remove obsolete build and GUI files, and introduce a new Docker Compose setup for microservices architecture. This change streamlines the project structure by eliminating legacy scripts and integrating a PostgreSQL database service alongside extraction and cleaning services.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""Builder para prompts de extracción.
|
||||
|
||||
Centraliza la construcción de instrucciones reducidas en base a:
|
||||
- selección de campos (paths de schema)
|
||||
- schema de referencia (ruta->tipo)
|
||||
- perfil (invoice/prenuevaeps)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from .profiles import get_profile
|
||||
from .config import get_allowed_document_types
|
||||
|
||||
|
||||
def _build_instructions() -> List[str]:
|
||||
tipos_documento = ", ".join(get_allowed_document_types())
|
||||
return [
|
||||
"Extrae únicamente los campos solicitados respetando jerarquía y listas",
|
||||
"Devuelve EXCLUSIVAMENTE un JSON válido y bien formado, sin texto adicional",
|
||||
"No inventes claves nuevas ni cambies nombres existentes",
|
||||
"Si un campo no existe, usa null o [] según corresponda",
|
||||
"Ten especial cuidado en no confundir el número de autorización con el código",
|
||||
f"Los únicos tipos de documento válidos para 'documento_forzate' son: {tipos_documento}. NIT no es válido para ese campo.",
|
||||
"Si entre los valores visibles aparece uno o varios tipos de documento válidos seguidos por su número (por ejemplo: 'CC 123456789' o 'EPT 123456789'), aunque no estén escritos con formato 'clave: valor', y existe el campo solicitado 'documento_forzate', guarda cada valor completo con su sigla exacta.",
|
||||
"Respeta la sigla exacta del documento encontrado. Ejemplo: si el documento dice 'EPT', devuelve 'EPT 123456789' y no lo conviertas a 'PT'.",
|
||||
"Solo si una ruta de observación, nota o texto aclaratorio figura explícitamente en 'campos_requeridos' (con el nombre exacto indicado), "
|
||||
"extrae el texto completo visible bajo su etiqueta en el documento (p. ej. línea «Observación:» …); no añadas ese campo si no está listado en 'campos_requeridos'.",
|
||||
]
|
||||
|
||||
_REGLAS_NOMENCLATURA = [
|
||||
"Usa EXACTAMENTE los nombres listados en 'campos_requeridos'",
|
||||
"No utilices sinónimos ni variantes (no cambies tildes ni mayúsculas/minúsculas)",
|
||||
"Mantén la estructura de rutas: 'items/*/subcampo' => {'items': [{'subcampo': ...}]}",
|
||||
"No cambies nombres de claves por 'equivalentes'",
|
||||
"Si un campo no existe, devuelve null (o []) usando el MISMO nombre solicitado",
|
||||
]
|
||||
|
||||
_NOTA_LISTAS = "Si se pide 'items/*/subtotal', la salida debe ser {'items': [{'subtotal': ...}, {'subtotal': ...}]}"
|
||||
|
||||
|
||||
class PromptBuilder:
|
||||
@staticmethod
|
||||
def _base_payload(
|
||||
profile_name: str,
|
||||
fields: List[str],
|
||||
schema: Dict[str, str],
|
||||
extra_meta: Dict[str, Any] | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
profile = get_profile(profile_name or "invoice")
|
||||
meta: Dict[str, Any] = {
|
||||
"perfil": profile.get("name", "invoice"),
|
||||
"descripcion": "Extracción estructurada desde OCR de facturas (sin datos personales reales)",
|
||||
}
|
||||
if extra_meta:
|
||||
meta.update(extra_meta)
|
||||
return {
|
||||
"meta": meta,
|
||||
"instrucciones": _build_instructions(),
|
||||
"campos_requeridos": fields,
|
||||
"schema_referencia": {k: schema[k] for k in fields if k in schema},
|
||||
"reglas_estrictas_nomenclatura": _REGLAS_NOMENCLATURA,
|
||||
"salida": {"tipo": "json_object", "raiz": None},
|
||||
"nota_listas": _NOTA_LISTAS,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def build(selected_fields: List[str], schema: Dict[str, str], profile_name: str | None = None) -> str:
|
||||
payload = PromptBuilder._base_payload(profile_name or "invoice", selected_fields, schema)
|
||||
return json.dumps(payload, indent=2, ensure_ascii=False)
|
||||
|
||||
@staticmethod
|
||||
def build_normalization_messages(profile_name: str, template: Dict[str, Any], source_json: Dict[str, Any]) -> List[Dict[str, str]]:
|
||||
system_txt = (
|
||||
"Eres un asistente que normaliza un JSON a un ESQUEMA CANÓNICO dado. "
|
||||
"Debes devolver EXCLUSIVAMENTE un JSON que respete exactamente la estructura y nombres del 'template'. "
|
||||
"Reglas: (1) No inventes claves; (2) No uses sinónimos ni variantes; (3) Respeta listas (si 'items' es lista, devuelve un array de objetos); "
|
||||
"(4) Si falta un valor, usa null o [] según corresponda; (5) No incluyas texto extra ni comentarios. "
|
||||
"(6) Los campos de texto largo (notas, observaciones, leyendas, pie de página, etc.): copia el texto COMPLETO "
|
||||
"del source_json sin resumir, sin omitir tramos ni acortar; respeta tildes y caracteres tal cual en el valor de origen."
|
||||
)
|
||||
user_obj = {
|
||||
"perfil": profile_name,
|
||||
"template": template,
|
||||
"source_json": source_json,
|
||||
"instrucciones": (
|
||||
"Mapea los valores del 'source_json' al 'template'. Si hay claves equivalentes en el source, "
|
||||
"elige siempre los nombres del template. No devuelvas rutas con '/', sino objetos anidados. "
|
||||
"Para cualquier cadena larga del documento (p. ej. notas legales o bloques de observación), "
|
||||
"transcribe el contenido literal completo; no uses elipsis ni versiones abreviadas."
|
||||
),
|
||||
}
|
||||
return [
|
||||
{"role": "system", "content": system_txt},
|
||||
{"role": "user", "content": json.dumps(user_obj, ensure_ascii=False)},
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def build_for_type(
|
||||
doc_type_name: str,
|
||||
selected_paths_for_type: List[str],
|
||||
schema_for_type: Dict[str, str],
|
||||
profile_name: str | None = None,
|
||||
) -> str:
|
||||
"""Construye un prompt reducido para un Tipo de Documento específico.
|
||||
|
||||
doc_type_name se usa en meta para trazabilidad; no afecta el esquema.
|
||||
"""
|
||||
payload = PromptBuilder._base_payload(
|
||||
profile_name or "invoice",
|
||||
selected_paths_for_type,
|
||||
schema_for_type,
|
||||
extra_meta={"tipo_documento": doc_type_name},
|
||||
)
|
||||
return json.dumps(payload, indent=2, ensure_ascii=False)
|
||||
Reference in New Issue
Block a user