Source: controllers/model.controller.js

/**
 * Model controller
 * 
 * Implements model management and operation endpoints
 */

import { modelService } from '../services/model.service.js';
import { APIError } from '../middleware/errorHandler.js';
import { logger } from '../utils/logger.js';
import { cache } from '../utils/cache.js';

export const modelController = {
  /**
   * List available models
   */
  listModels: async (req, res, next) => {
    try {
      // Check cache first
      const cacheKey = 'models_list';
      const cachedModels = cache.get(cacheKey);
      
      if (cachedModels) {
        return res.json(cachedModels);
      }
      
      const models = await modelService.listModels(req.query);
      
      // Store in cache
      cache.set(cacheKey, models);
      
      res.json(models);
    } catch (error) {
      next(new APIError(`Failed to retrieve models: ${error.message}`, 500));
    }
  },
  
  /**
   * Get details for a specific model
   */
  getModelDetails: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const cacheKey = `model_${modelId}`;
      const cachedModel = cache.get(cacheKey);
      
      if (cachedModel) {
        return res.json(cachedModel);
      }
      
      const model = await modelService.getModelDetails(modelId);
      
      if (!model) {
        return next(new APIError(`Model not found: ${modelId}`, 404, 'MODEL_NOT_FOUND'));
      }
      
      // Store in cache
      cache.set(cacheKey, model);
      
      res.json(model);
    } catch (error) {
      next(new APIError(`Failed to retrieve model details: ${error.message}`, 500));
    }
  },
  
  /**
   * List versions for a specific model
   */
  listModelVersions: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const versions = await modelService.listModelVersions(modelId, req.query);
      
      if (!versions) {
        return next(new APIError(`Model not found: ${modelId}`, 404, 'MODEL_NOT_FOUND'));
      }
      
      res.json(versions);
    } catch (error) {
      next(new APIError(`Failed to retrieve model versions: ${error.message}`, 500));
    }
  },
  
  /**
   * Get details for a specific model version
   */
  getModelVersionDetails: async (req, res, next) => {
    try {
      const { modelId, versionId } = req.params;
      const version = await modelService.getModelVersionDetails(modelId, versionId);
      
      if (!version) {
        return next(new APIError(`Model version not found: ${modelId}/${versionId}`, 404, 'MODEL_VERSION_NOT_FOUND'));
      }
      
      res.json(version);
    } catch (error) {
      next(new APIError(`Failed to retrieve model version details: ${error.message}`, 500));
    }
  },
  
  /**
   * Make a prediction using a model
   */
  predictWithModel: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { inputs, options } = req.body;
      
      if (!inputs) {
        return next(new APIError('Missing required input data', 400, 'MISSING_INPUTS'));
      }
      
      const startTime = Date.now();
      const prediction = await modelService.predict(modelId, inputs, options);
      const duration = Date.now() - startTime;
      
      // Log the model call
      logger.modelCall(modelId, 'predict', duration, true);
      
      res.json({
        model: modelId,
        prediction,
        metadata: {
          processingTime: duration,
          timestamp: new Date().toISOString()
        }
      });
    } catch (error) {
      logger.modelCall(req.params.modelId, 'predict', 0, false);
      next(new APIError(`Prediction failed: ${error.message}`, 500));
    }
  },
  
  /**
   * Analyze data using a model
   */
  analyzeWithModel: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { data, options } = req.body;
      
      if (!data) {
        return next(new APIError('Missing required data for analysis', 400, 'MISSING_DATA'));
      }
      
      const startTime = Date.now();
      const analysis = await modelService.analyze(modelId, data, options);
      const duration = Date.now() - startTime;
      
      // Log the model call
      logger.modelCall(modelId, 'analyze', duration, true);
      
      res.json({
        model: modelId,
        analysis,
        metadata: {
          processingTime: duration,
          timestamp: new Date().toISOString()
        }
      });
    } catch (error) {
      logger.modelCall(req.params.modelId, 'analyze', 0, false);
      next(new APIError(`Analysis failed: ${error.message}`, 500));
    }
  },
  
  /**
   * Generate content using a model
   */
  generateWithModel: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { prompt, options } = req.body;
      
      if (!prompt) {
        return next(new APIError('Missing required prompt for generation', 400, 'MISSING_PROMPT'));
      }
      
      const startTime = Date.now();
      const generated = await modelService.generate(modelId, prompt, options);
      const duration = Date.now() - startTime;
      
      // Log the model call
      logger.modelCall(modelId, 'generate', duration, true);
      
      res.json({
        model: modelId,
        generated,
        metadata: {
          processingTime: duration,
          timestamp: new Date().toISOString()
        }
      });
    } catch (error) {
      logger.modelCall(req.params.modelId, 'generate', 0, false);
      next(new APIError(`Generation failed: ${error.message}`, 500));
    }
  },
  
  /**
   * Create embeddings using a model
   */
  embedWithModel: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { texts, options } = req.body;
      
      if (!texts || !Array.isArray(texts)) {
        return next(new APIError('Missing or invalid texts for embedding', 400, 'INVALID_TEXTS'));
      }
      
      const startTime = Date.now();
      const embeddings = await modelService.createEmbeddings(modelId, texts, options);
      const duration = Date.now() - startTime;
      
      // Log the model call
      logger.modelCall(modelId, 'embed', duration, true);
      
      res.json({
        model: modelId,
        embeddings,
        metadata: {
          processingTime: duration,
          timestamp: new Date().toISOString()
        }
      });
    } catch (error) {
      logger.modelCall(req.params.modelId, 'embed', 0, false);
      next(new APIError(`Embedding failed: ${error.message}`, 500));
    }
  },
  
  /**
   * Process batch of inputs
   */
  batchProcess: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { operation, batch, options } = req.body;
      
      if (!operation || !batch || !Array.isArray(batch)) {
        return next(new APIError('Missing or invalid batch operation parameters', 400, 'INVALID_BATCH'));
      }
      
      const startTime = Date.now();
      const results = await modelService.processBatch(modelId, operation, batch, options);
      const duration = Date.now() - startTime;
      
      // Log the model call
      logger.modelCall(modelId, `batch_${operation}`, duration, true);
      
      res.json({
        model: modelId,
        operation,
        results,
        metadata: {
          processingTime: duration,
          timestamp: new Date().toISOString(),
          batchSize: batch.length
        }
      });
    } catch (error) {
      logger.modelCall(req.params.modelId, 'batch', 0, false);
      next(new APIError(`Batch processing failed: ${error.message}`, 500));
    }
  },
  
  /**
   * Fine-tune a model
   */
  fineTuneModel: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { trainingData, hyperparameters, options } = req.body;
      
      if (!trainingData) {
        return next(new APIError('Missing training data for fine-tuning', 400, 'MISSING_TRAINING_DATA'));
      }
      
      const startTime = Date.now();
      const job = await modelService.startFineTune(modelId, trainingData, hyperparameters, options);
      const duration = Date.now() - startTime;
      
      // Log the model call
      logger.modelCall(modelId, 'fine_tune', duration, true);
      
      res.json({
        model: modelId,
        job,
        metadata: {
          timestamp: new Date().toISOString()
        }
      });
    } catch (error) {
      logger.modelCall(req.params.modelId, 'fine_tune', 0, false);
      next(new APIError(`Fine-tuning failed: ${error.message}`, 500));
    }
  },
  
  /**
   * Get fine-tune job status
   */
  getFineTuneStatus: async (req, res, next) => {
    try {
      const { modelId, jobId } = req.params;
      const status = await modelService.getFineTuneStatus(modelId, jobId);
      
      if (!status) {
        return next(new APIError(`Fine-tune job not found: ${jobId}`, 404, 'JOB_NOT_FOUND'));
      }
      
      res.json({
        model: modelId,
        job: jobId,
        status
      });
    } catch (error) {
      next(new APIError(`Failed to retrieve fine-tune status: ${error.message}`, 500));
    }
  },
  
  /**
   * Register a new model
   */
  registerModel: async (req, res, next) => {
    try {
      const { name, description, type, initialVersion, metadata } = req.body;
      
      if (!name || !type) {
        return next(new APIError('Missing required model information', 400, 'MISSING_MODEL_INFO'));
      }
      
      const model = await modelService.registerModel(name, description, type, initialVersion, metadata);
      
      res.status(201).json(model);
    } catch (error) {
      next(new APIError(`Failed to register model: ${error.message}`, 500));
    }
  },
  
  /**
   * Update an existing model
   */
  updateModel: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { name, description, type, status, metadata } = req.body;
      
      const model = await modelService.updateModel(modelId, { name, description, type, status, metadata });
      
      if (!model) {
        return next(new APIError(`Model not found: ${modelId}`, 404, 'MODEL_NOT_FOUND'));
      }
      
      // Invalidate cache
      cache.delete(`model_${modelId}`);
      cache.delete('models_list');
      
      res.json(model);
    } catch (error) {
      next(new APIError(`Failed to update model: ${error.message}`, 500));
    }
  },
  
  /**
   * Delete a model
   */
  deleteModel: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      
      const success = await modelService.deleteModel(modelId);
      
      if (!success) {
        return next(new APIError(`Model not found: ${modelId}`, 404, 'MODEL_NOT_FOUND'));
      }
      
      // Invalidate cache
      cache.delete(`model_${modelId}`);
      cache.delete('models_list');
      
      res.status(204).end();
    } catch (error) {
      next(new APIError(`Failed to delete model: ${error.message}`, 500));
    }
  },
  
  /**
   * Create a new model version
   */
  createModelVersion: async (req, res, next) => {
    try {
      const { modelId } = req.params;
      const { version, artifacts, metadata } = req.body;
      
      if (!version || !artifacts) {
        return next(new APIError('Missing required version information', 400, 'MISSING_VERSION_INFO'));
      }
      
      const modelVersion = await modelService.createModelVersion(modelId, version, artifacts, metadata);
      
      res.status(201).json(modelVersion);
    } catch (error) {
      next(new APIError(`Failed to create model version: ${error.message}`, 500));
    }
  },
  
  /**
   * Update a model version
   */
  updateModelVersion: async (req, res, next) => {
    try {
      const { modelId, versionId } = req.params;
      const { status, metadata } = req.body;
      
      const modelVersion = await modelService.updateModelVersion(modelId, versionId, { status, metadata });
      
      if (!modelVersion) {
        return next(new APIError(`Model version not found: ${modelId}/${versionId}`, 404, 'MODEL_VERSION_NOT_FOUND'));
      }
      
      res.json(modelVersion);
    } catch (error) {
      next(new APIError(`Failed to update model version: ${error.message}`, 500));
    }
  },
  
  /**
   * Delete a model version
   */
  deleteModelVersion: async (req, res, next) => {
    try {
      const { modelId, versionId } = req.params;
      
      const success = await modelService.deleteModelVersion(modelId, versionId);
      
      if (!success) {
        return next(new APIError(`Model version not found: ${modelId}/${versionId}`, 404, 'MODEL_VERSION_NOT_FOUND'));
      }
      
      res.status(204).end();
    } catch (error) {
      next(new APIError(`Failed to delete model version: ${error.message}`, 500));
    }
  },
};

export default modelController;