Files
luma/backend/app/models/schema_models.py

97 lines
4.1 KiB
Python

"""
Modelos Pydantic para schemas personalizables.
Permite definir schemas dinámicos desde el frontend para extracción de datos.
"""
from pydantic import BaseModel, Field, field_validator
from typing import List, Optional
from enum import Enum
from datetime import datetime
class FieldType(str, Enum):
"""Tipos de campos soportados para extracción"""
STRING = "string"
INTEGER = "integer"
FLOAT = "float"
BOOLEAN = "boolean"
ARRAY_STRING = "array_string"
ARRAY_INTEGER = "array_integer"
ARRAY_FLOAT = "array_float"
DATE = "date"
class SchemaField(BaseModel):
"""Definición de un campo del schema"""
name: str = Field(..., description="Nombre del campo (snake_case)", min_length=1)
type: FieldType = Field(..., description="Tipo de dato del campo")
description: str = Field(..., description="Descripción clara para el LLM sobre qué extraer", min_length=1)
required: bool = Field(default=False, description="¿Es obligatorio extraer este campo?")
# Validaciones opcionales
min_value: Optional[float] = Field(None, description="Valor mínimo (para integer/float)")
max_value: Optional[float] = Field(None, description="Valor máximo (para integer/float)")
pattern: Optional[str] = Field(None, description="Patrón regex para validar strings")
@field_validator('name')
@classmethod
def validate_field_name(cls, v: str) -> str:
"""Valida que el nombre del campo sea snake_case válido"""
if not v.replace('_', '').isalnum():
raise ValueError("El nombre del campo debe ser snake_case alfanumérico")
if v[0].isdigit():
raise ValueError("El nombre del campo no puede empezar con número")
return v.lower()
@field_validator('min_value', 'max_value')
@classmethod
def validate_numeric_constraints(cls, v: Optional[float], info) -> Optional[float]:
"""Valida que min/max solo se usen con tipos numéricos"""
if v is not None:
field_type = info.data.get('type')
if field_type not in [FieldType.INTEGER, FieldType.FLOAT, FieldType.ARRAY_INTEGER, FieldType.ARRAY_FLOAT]:
raise ValueError(f"min_value/max_value solo aplican a campos numéricos, no a {field_type}")
return v
class CustomSchema(BaseModel):
"""Schema personalizable por el usuario para extracción de datos"""
schema_id: Optional[str] = Field(None, description="ID único del schema (generado automáticamente si no se provee)")
schema_name: str = Field(..., description="Nombre descriptivo del schema", min_length=1, max_length=100)
description: str = Field(..., description="Descripción de qué extrae este schema", min_length=1, max_length=500)
fields: List[SchemaField] = Field(..., description="Lista de campos a extraer", min_items=1, max_items=200)
# Metadata
created_at: Optional[str] = Field(None, description="Timestamp de creación ISO")
updated_at: Optional[str] = Field(None, description="Timestamp de última actualización ISO")
tema: Optional[str] = Field(None, description="Tema asociado (si es específico de un tema)")
is_global: bool = Field(default=False, description="¿Disponible para todos los temas?")
@field_validator('fields')
@classmethod
def validate_unique_field_names(cls, v: List[SchemaField]) -> List[SchemaField]:
"""Valida que no haya nombres de campos duplicados"""
field_names = [field.name for field in v]
if len(field_names) != len(set(field_names)):
raise ValueError("Los nombres de campos deben ser únicos en el schema")
return v
@field_validator('schema_name')
@classmethod
def validate_schema_name(cls, v: str) -> str:
"""Limpia y valida el nombre del schema"""
return v.strip()
class SchemaListResponse(BaseModel):
"""Response para listar schemas"""
schemas: List[CustomSchema]
total: int
class SchemaValidationResponse(BaseModel):
"""Response para validación de schema"""
valid: bool
message: str
json_schema: Optional[dict] = None
errors: Optional[List[str]] = None