DEV Community

Cover image for Cómo integrar Azure Text Analytics en una aplicación de análisis de feedback
Daniel J. Saldaña
Daniel J. Saldaña

Posted on • Originally published at danieljsaldana.dev on

Cómo integrar Azure Text Analytics en una aplicación de análisis de feedback

En la era digital, comprender las percepciones y emociones de los usuarios es esencial para mejorar productos y servicios. Microsoft Azure Text Analytics ofrece una solución robusta para analizar el sentimiento y las frases clave de los textos. En este post, te guiaré a través de una prueba de concepto para integrar Azure Text Analytics en una aplicación de análisis de feedback.

Paso 1: Configurar el entorno de desarrollo

Antes de comenzar, asegúrate de tener las siguientes herramientas y servicios configurados:

  • Node.js y npm instalados en tu máquina local.
  • Una cuenta de Azure con acceso al servicio Text Analytics.
  • Un entorno de base de datos PostgreSQL.

Paso 2: Preparar el frontend

Nuestra aplicación de feedback se construirá usando React. Crearemos un componente para enviar feedback y visualizar el análisis de sentimientos.

  1. Crea un nuevo proyecto React : Utiliza create-react-app para configurar tu entorno de frontend.

  2. Integra el componente AnalyzeFeedback : Este componente es responsable de enviar el título del feedback y mostrar los resultados del análisis.


'use client'

import { useEffect, useState } from 'react';
import styles from './AnalyzeFeedback.module.scss';
import ClassName from '@/types/ClassName';

interface AnalyzeFeedbackProps extends ClassName {
  title: string;
}

const AnalyzeFeedback = ({ className, title }: AnalyzeFeedbackProps) => {
  const [score, setScore] = useState<number | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setIsLoading(true);
    fetch('https://api.danieljsaldana.dev/api/getfeedbackscores', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ title }),
    })
      .then(response => response.json())
      .then(data => {
        if (data.compositeScore !== undefined) {
          setScore(data.compositeScore);
        } else {
          setScore(null);
        }
      })
      .catch(() => setScore(null))
      .finally(() => setIsLoading(false));
  }, [title]);

  const getBackgroundColor = (score: number | null) => {
    if (score === null) {
      return '#d3d3d3';
    } else if (score > 80) {
      return '#0ac769';
    } else if (score >= 50 && score <= 79) {
      return '#ffad00';
    } else {
      return '#ea001e';
    }
  };

  const backgroundColor = getBackgroundColor(score);
  const circumference = 301.59289474462014;
  const offset = score !== null ? ((100 - score) / 100) * circumference : 0;

  return (
    <div className={`rounded-full h-[24px] w-[22px] ${className ?? ''}`}>
      <svg width="24" height="24" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
        <g shapeRendering="geometricPrecision">
          <circle cx="64" cy="64" fill={backgroundColor} r="64" />
          <circle cx="64" cy="64" fill="none" r="48" stroke="rgba(0,0,0,.1)" strokeWidth="10" />
          {!isLoading && score !== null && (
            <circle
              cx="64"
              cy="64"
              fill="none"
              r="48"
              stroke="white"
              strokeDasharray={`${circumference} ${circumference}`}
              strokeDashoffset={offset}
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth="10"
              className="score_progress__quTNG" 
            />
          )}
          {(isLoading || score === null) && (
            <svg className={`${styles.withIconIcon__MHUeb} ${styles.fadeIn}`} data-testid="geist-icon" fill="none" height="70" shapeRendering="geometricPrecision" stroke="#999" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="70" x="29" y="29">
              <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
            </svg>
          )}
          {!isLoading && score !== null && (
            <text
              fill="white"
              fontSize="42"
              fontWeight="500"
              textAnchor="middle"
              x="64"
              y="79"
            >
              {score}
            </text>
          )}
        </g>
      </svg>
    </div>
  );
};

export default AnalyzeFeedback;

Enter fullscreen mode Exit fullscreen mode
  1. Estiliza tu componente : Usa los estilos predefinidos para hacer que tu componente sea visualmente atractivo y claro para los usuarios.
@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.fadeIn {
    animation: fadeIn ease-in 1s;
    animation-fill-mode: forwards;
}

Enter fullscreen mode Exit fullscreen mode

Paso 3: Configurar el backend

El backend de nuestra aplicación se construirá con Node.js y se comunicará con los servicios de Azure para analizar el feedback.

  1. Establece la API de análisis de feedback : Crea una API que reciba el feedback, analice el sentimiento y las frases clave usando Azure Text Analytics, e inserte los resultados en la base de datos.
import { TextAnalyticsClient, AzureKeyCredential } from '@azure/ai-text-analytics';
import { enableCors } from "@/src/middleware/enableCors";
import { methodValidator } from "@/src/utils/methodValidator";
import { insertFeedback } from "@/src/core/insertFeedback"; 
import dotenv from 'dotenv';

dotenv.config();

const textAnalyticsClient = new TextAnalyticsClient(
    process.env.AZURE_TEXT_ANALYTICS_ENDPOINT, 
    new AzureKeyCredential(process.env.AZURE_TEXT_ANALYTICS_KEY)
);

async function analyzeFeedback(req, res) {
    try {
        await methodValidator(req, res, 'POST');
        if (res.headersSent) return;

        const { title, text, user } = req.body;
        if (!title || !text || !user) {
            return res.status(400).json({ error: 'Faltan datos requeridos para el análisis' });
        }

        const sentimentResult = await textAnalyticsClient.analyzeSentiment([text]);
        const sentiment = sentimentResult[0];

        const keyPhrasesResult = await textAnalyticsClient.extractKeyPhrases([text]);
        const keyPhrases = keyPhrasesResult[0];

        await insertFeedback(
            title, 
            sentiment.sentiment, 
            sentiment.confidenceScores.positive, 
            sentiment.confidenceScores.neutral, 
            sentiment.confidenceScores.negative, 
            user
        );

        res.status(200).json({
            title,
            user,
            sentiment: sentiment.sentiment,
            confidenceScores: sentiment.confidenceScores,
            keyPhrases: keyPhrases.keyPhrases,
        });
    } catch (error) {
        console.error('Error al analizar el feedback:', error);
        res.status(500).json({ error: `Error al analizar el feedback: ${error.message}` });
    }
}

export default enableCors(analyzeFeedback);

Enter fullscreen mode Exit fullscreen mode
  1. Desarrolla la API para obtener puntajes de feedback : Esta API recupera los promedios de los puntajes de feedback basados en el título proporcionado.
import { enableCors } from "@/src/middleware/enableCors";
import { methodValidator } from "@/src/utils/methodValidator";
import { sanitizeTitleForFilename } from "@/src/utils/sanitizeTitleForFilename";
import { getAverageFeedbackScores } from "@/src/core/getAverageFeedbackScores";
import dotenv from 'dotenv';

dotenv.config();

export async function getFeedbackScores(req, res) {
    await methodValidator(req, res, 'POST'); 
    if (res.headersSent) return; 

    const { title } = req.body; 
    if (!title) { 
        return res.status(400).json({ error: 'Falta el título para el análisis' });
    }

    const sanitizedTitle = sanitizeTitleForFilename(title); 
    console.log('Título sanitizado:', sanitizedTitle);

    try {
        const averages = await getAverageFeedbackScores(sanitizedTitle);

        if (!averages || averages.average_positive === 0 && averages.average_neutral === 0 && averages.average_negative === 0) {
            return res.status(200).json({ error: 'No se encontraron datos para el título proporcionado', compositeScore: null });
        }

        const avgPositive = parseFloat(averages.average_positive);
        const avgNeutral = parseFloat(averages.average_neutral);
        const avgNegative = parseFloat(averages.average_negative);

        console.log('Promedios convertidos:', avgPositive, avgNeutral, avgNegative);

        let compositeScore = (avgPositive + 0.5 * avgNeutral - avgNegative);
        compositeScore = Math.max(0, Math.min(1, compositeScore)) * 100;

        compositeScore = Math.round(compositeScore);

        console.log('Puntuación compuesta:', compositeScore);
        res.status(200).json({ compositeScore: compositeScore });
    } catch (error) {
        console.error('Error al obtener los promedios del feedback:', error);
        res.status(500).json({ error: `Error al obtener los promedios del feedback: ${error.message}` });
    }
}

export default enableCors(getFeedbackScores);

Enter fullscreen mode Exit fullscreen mode
  1. Implementa funciones de base de datos : Usa estas funciones para interactuar con tu base de datos PostgreSQL y manejar la inserción y recuperación de datos de feedback.
import { sql } from '@vercel/postgres'; 

export async function getAverageFeedbackScores(title) {
    try {
        const result = await sql`
            SELECT 
                AVG(positive) AS average_positive, 
                AVG(neutral) AS average_neutral, 
                AVG(negative) AS average_negative 
            FROM post_feedback 
            WHERE title = ${title}
            GROUP BY title;
        `;

        if (result.rows && result.rows.length > 0) {
            return result.rows[0];
        } else {
            return { average_positive: 0, average_neutral: 0, average_negative: 0 };
        }
    } catch (error) {
        console.error(`Error al obtener los promedios de feedback para el título: ${title}`, error);
        throw error;
    }
}

Enter fullscreen mode Exit fullscreen mode

y

import { sql } from '@vercel/postgres';

export async function insertFeedback(title, sentiment, positive, neutral, negative, user) {
    console.log(`Insertando feedback para el título: ${title}`);

    try {
        await sql`
            INSERT INTO post_feedback
            (title, sentiment, positive, neutral, negative, "user", last_updated)
            VALUES 
            (${title}, ${sentiment}, ${positive}, ${neutral}, ${negative}, ${user}, CURRENT_TIMESTAMP)
            ON CONFLICT (title, "user") -- Actualiza esto para que coincida con la nueva restricción única
            DO UPDATE SET 
                sentiment = EXCLUDED.sentiment,
                positive = EXCLUDED.positive,
                neutral = EXCLUDED.neutral,
                negative = EXCLUDED.negative,
                last_updated = CURRENT_TIMESTAMP;
        `;
        console.log(`Feedback insertado con éxito para el título: ${title}`);
    } catch (error) {
        console.error(`Error al insertar feedback para el título: ${title}`, error);
        throw new Error(`Error al insertar feedback: ${error.message}`);
    }
}

Enter fullscreen mode Exit fullscreen mode

Paso 4: Configuración de la base de datos

Antes de que puedas comenzar a almacenar y recuperar datos de feedback, necesitas configurar tu base de datos PostgreSQL. Usa la siguiente query SQL para crear la tabla necesaria para almacenar los datos de feedback:

CREATE TABLE post_feedback (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    sentiment VARCHAR(50) NOT NULL,
    positive NUMERIC NOT NULL,
    neutral NUMERIC NOT NULL,
    negative NUMERIC NOT NULL,
    "user" VARCHAR(255) NOT NULL,
    last_updated TIMESTAMP NOT NULL,
    UNIQUE(title, "user")
);

Enter fullscreen mode Exit fullscreen mode

Esta tabla almacenará cada pieza de feedback junto con el análisis de sentimientos y la marca de tiempo de la última actualización. Asegúrate de que tu base de datos esté en funcionamiento y accesible desde tu backend antes de continuar.

Paso 5: Prueba de concepto

Para probar la integración completa de tu sistema, utiliza Postman para simular solicitudes de cliente a tu backend. A continuación, te proporciono una guía para configurar las solicitudes en Postman:

Configuración en Postman:

  1. POST para analizar feedback :

  2. POST para obtener puntajes de feedback :

Asegúrate de tener tu servidor backend en ejecución antes de enviar las solicitudes desde Postman. Estas solicitudes simularán la interacción entre el frontend y el backend, permitiéndote evaluar la funcionalidad completa de tu aplicación de análisis de feedback.

Después de configurar y ejecutar estas pruebas, deberías poder ver cómo tu aplicación procesa y responde al feedback, cómo se almacena en la base de datos y cómo se recuperan y calculan los promedios de los puntajes de sentimiento. Esto completará tu prueba de concepto, demostrando la funcionalidad y la integración entre todas las partes de tu aplicación.

Y con esto, has completado la integración de Azure Text Analytics en tu aplicación de análisis de feedback. ¡Felicidades por llegar hasta aquí! Si tienes alguna pregunta o comentario, no dudes en dejarlo a continuación.

Top comments (0)