Caché de Proyectos

La API de Caché de Proyectos gestiona la carga en memoria de proyectos para operaciones de la API. Entender cuándo es necesario cargar proyectos es esencial para un uso eficiente de la API.

Conceptos Clave

Arquitectura Unificada de Caché

La API y la interfaz de usuario (UI) comparten la misma caché en memoria. Cuando cargas un proyecto vía API, es la misma caché que usa la UI. Esto significa:

  • Estado Compartido: Las operaciones de la API ven los mismos datos que los usuarios de la UI
  • Resultados Compartidos: Los resultados de ejecución son visibles tanto para API como para UI
  • Sin Divergencia: Es imposible que API y UI tengan vistas diferentes de un proyecto

Categorías de Operaciones

Las operaciones API se dividen en tres categorías con diferentes requisitos de caché:

Categoría Descripción ¿Requiere carga del proyecto? Ejemplos
Direct DB Operaciones de solo lectura No Endpoints GET, gestión de tenant/usuario
Auto-Carga Operaciones de modificación No (auto-carga) POST/PUT/DELETE sobre investigaciones, notebooks, bloques
Requiere Carga Operaciones de ejecución Ejecutar notebook, obtener resultados de ejecución

Patrón de Auto-Carga (Flujo Simplificado)

Para la mayoría de operaciones CRUD, no necesitas cargar explícitamente el proyecto. La API carga automáticamente el proyecto cuando es necesario:

# Flujo ANTIGUO (ya no es necesario para CRUD):
# manager.load_project(project_id)  # ¡No requerido!

# Flujo NUEVO - solo llama la operación directamente:
response = requests.put(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/{notebook_id}",
    json={"Name": "Nombre Actualizado"},
    headers=headers
)
# El proyecto se carga automáticamente si es necesario

Cuándo SÍ se Requiere Carga Explícita

La carga explícita del proyecto sigue siendo requerida para operaciones de ejecución:

  • POST /execution/notebook/{notebookId} - Ejecutar notebook
  • GET /execution/notebook/{notebookId}/results - Obtener resultados de ejecución
  • GET /execution/status/{notebookId} - Consultar estado de ejecución

Cargar Proyecto en Caché

GET /api/{tenantId}/project/{projectId}/load

Carga un proyecto en la caché compartida. Usa esto antes de ejecutar notebooks.

Parámetros de Ruta

Parámetro Tipo Obligatorio Descripción
tenantId GUID Identificador del tenant
projectId GUID Identificador del proyecto

Respuesta (200 OK)

{
  "projectId": "87654321-4321-4321-4321-210987654321",
  "projectName": "Análisis de Ordenes de Compra",
  "tenantName": "acme-corp",
  "investigationCount": 5,
  "notebookCount": 12,
  "datasetCount": 3,
  "loadedFromCache": false,
  "message": "Proyecto cargado desde la base de datos"
}

Campos de Respuesta

Campo Tipo Descripción
projectId GUID Identificador del proyecto
projectName cadena Nombre del proyecto
tenantName cadena Nombre del tenant
investigationCount entero Número de investigaciones
notebookCount entero Número de notebooks
datasetCount entero Número de conjuntos de datos
loadedFromCache booleano Verdadero si ya estaba en caché, falso si se cargó desde la base de datos
message cadena Mensaje de estado legible por humanos

Comportamiento de la Caché

Escenario Respuesta Rendimiento
Primera llamada (fallo de caché) loadedFromCache: false ~1000ms (consulta a DB)
Llamadas siguientes (acierto de caché) loadedFromCache: true ~75ms (13x más rápido)
Después de 30 min sin actividad Caché expira La siguiente llamada recarga

Propiedades de la Caché

  • Duración: 30 minutos después del último acceso
  • Auto-refresco: Cualquier llamada API al proyecto reinicia el temporizador de 30 minutos
  • Compartida: Misma caché usada por UI y API
  • Gestión de Memoria: Limpieza automática al 90% de presión de memoria

Descargar Proyecto de Caché

DELETE /api/{tenantId}/project/{projectId}/unload

Elimina un proyecto de la caché, liberando memoria.

Parámetros de Ruta

Parámetro Tipo Obligatorio Descripción
tenantId GUID Identificador del tenant
projectId GUID Identificador del proyecto

Respuesta (200 OK)

{
  "projectId": "87654321-4321-4321-4321-210987654321",
  "wasInCache": true,
  "message": "Proyecto descargado de la caché exitosamente"
}

Ejemplos de Flujos de Trabajo

Flujo A: Operaciones CRUD (Auto-Carga)

Para crear, actualizar o eliminar investigaciones, notebooks o bloques:

import requests

headers = {"Authorization": f"Bearer {API_KEY}"}

# Solo llama la operación directamente - no se necesita cargar!
response = requests.post(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/investigation",
    json={"name": "Nueva Investigación", "description": "Creada vía API"},
    headers=headers
)
# El proyecto se carga automáticamente si es necesario

Flujo B: Ejecución de Notebook (Requiere Carga)

Para ejecutar notebooks y obtener resultados:

import requests
import time

headers = {"Authorization": f"Bearer {API_KEY}"}

# Paso 1: Cargar proyecto (REQUERIDO para ejecución)
response = requests.get(
    f"{BASE_URL}/api/{TENANT_ID}/project/{PROJECT_ID}/load",
    headers=headers
)
print(f"Proyecto cargado: {response.json()['projectName']}")

# Paso 2: Ejecutar notebook
response = requests.post(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/execution/notebook/{NOTEBOOK_ID}",
    headers=headers
)
print(f"Ejecución en cola: {response.json()['status']}")

# Paso 3: Consultar hasta terminar
while True:
    response = requests.get(
        f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/execution/status/{NOTEBOOK_ID}",
        headers=headers
    )
    status = response.json()
    print(f"Estado: {status['status']} ({status['progress']}%)")

    if status['status'] == 'Completed':
        break
    time.sleep(2)

# Paso 4: Obtener resultados
response = requests.get(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/execution/notebook/{NOTEBOOK_ID}/results",
    headers=headers
)
results = response.json()

# Paso 5: Descargar proyecto (limpieza opcional)
requests.delete(
    f"{BASE_URL}/api/{TENANT_ID}/project/{PROJECT_ID}/unload",
    headers=headers
)

Ejemplos de Implementación

cURL

# Cargar proyecto en caché
curl -X GET "https://your-mindzie-instance.com/api/12345678-1234-1234-1234-123456789012/project/87654321-4321-4321-4321-210987654321/load" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Descargar proyecto de caché
curl -X DELETE "https://your-mindzie-instance.com/api/12345678-1234-1234-1234-123456789012/project/87654321-4321-4321-4321-210987654321/unload" \
  -H "Authorization: Bearer YOUR_API_KEY"

Python

import requests

TENANT_ID = '12345678-1234-1234-1234-123456789012'
BASE_URL = 'https://your-mindzie-instance.com'

class ProjectCacheManager:
    def __init__(self, token):
        self.headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
        self.loaded_projects = set()

    def load_project(self, project_id):
        """Carga un proyecto en caché (requerido para operaciones de ejecución)."""
        url = f'{BASE_URL}/api/{TENANT_ID}/project/{project_id}/load'
        response = requests.get(url, headers=self.headers)
        response.raise_for_status()

        result = response.json()
        self.loaded_projects.add(project_id)

        status = "desde caché" if result['loadedFromCache'] else "desde base de datos"
        print(f"Proyecto '{result['projectName']}' cargado {status}")

        return result

    def unload_project(self, project_id):
        """Descarga un proyecto de la caché."""
        url = f'{BASE_URL}/api/{TENANT_ID}/project/{project_id}/unload'
        response = requests.delete(url, headers=self.headers)
        response.raise_for_status()

        self.loaded_projects.discard(project_id)
        return response.json()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        for project_id in list(self.loaded_projects):
            self.unload_project(project_id)

# Uso con gestor de contexto
with ProjectCacheManager('your-api-key') as cache:
    result = cache.load_project('87654321-4321-4321-4321-210987654321')
    # Ejecutar notebooks aquí...
# Proyectos se descargan automáticamente al salir

JavaScript/Node.js

const TENANT_ID = '12345678-1234-1234-1234-123456789012';
const BASE_URL = 'https://your-mindzie-instance.com';

class ProjectCacheManager {
  constructor(token) {
    this.headers = {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    };
    this.loadedProjects = new Set();
  }

  async loadProject(projectId) {
    const url = `${BASE_URL}/api/${TENANT_ID}/project/${projectId}/load`;
    const response = await fetch(url, { headers: this.headers });

    if (!response.ok) throw new Error(`Falló: ${response.status}`);

    const result = await response.json();
    this.loadedProjects.add(projectId);

    console.log(`Cargado: ${result.projectName} (desde caché: ${result.loadedFromCache})`);
    return result;
  }

  async unloadProject(projectId) {
    const url = `${BASE_URL}/api/${TENANT_ID}/project/${projectId}/unload`;
    const response = await fetch(url, {
      method: 'DELETE',
      headers: this.headers
    });

    this.loadedProjects.delete(projectId);
    return response.json();
  }

  async unloadAll() {
    await Promise.all(
      Array.from(this.loadedProjects).map(id => this.unloadProject(id))
    );
  }
}

// Uso
const cache = new ProjectCacheManager('your-api-key');
try {
  await cache.loadProject('87654321-4321-4321-4321-210987654321');
  // Ejecutar notebooks aquí...
} finally {
  await cache.unloadAll();
}

Buenas Prácticas

  1. Operaciones CRUD: No cargues explícitamente - deja que la auto-carga lo maneje
  2. Operaciones de Ejecución: Siempre carga el proyecto primero
  3. Clientes de Larga Duración: Descarga proyectos cuando termines para liberar memoria
  4. Gestores de Contexto: Usa sentencias with (Python) o try/finally para limpieza
  5. Conciencia de Memoria: La caché se limpia automáticamente al 90% de presión, pero descargar explícitamente es mejor
  6. Caché Compartida: Recuerda que los usuarios de UI ven el mismo estado del proyecto que tus operaciones API