From bb6e84b3208682dbcbcb2ed169a27a7eae0af36c Mon Sep 17 00:00:00 2001 From: Patricio Julian Date: Tue, 13 Nov 2018 21:41:38 +0000 Subject: [PATCH 01/10] Update index.js --- routes/index.js | 318 +++++++++++++++++++++++++----------------------- 1 file changed, 169 insertions(+), 149 deletions(-) diff --git a/routes/index.js b/routes/index.js index 90fe510..4c0dc60 100644 --- a/routes/index.js +++ b/routes/index.js @@ -12,57 +12,59 @@ const striptags = require('striptags'); const moment = require('moment'); -// Herramientas de procesamiento y algoritmica. +// Herramientas de matematica y algoritmica. +const math = require('mathjs'); const compare = require('hamming-distance'); +const hamming = require('compute-hamming'); +let distance = require('euclidean-distance'); // Herramientas de NLP const natural = require('natural'); const lorca = require('lorca-nlp'); -// Vamos a partir de una URL de una noticia. - // Usamos el dataset de FOPEA de mapas de medios para indicar: - // Si el articulo viene de un medio que es parte de un grupo concentrado de medios y cuál. - // Forma de financiamiento y monetización del medio. +/* Vamos a partir de una URL de una noticia. + Usamos el dataset de FOPEA de mapas de medios para indicar: + Si el articulo viene de un medio que es parte de un grupo concentrado de medios y cuál. + Forma de financiamiento y monetización del medio. -// Luego se procesa el articulo. Se extrae el HTML con cheerio. Se tokeniza el corpus del articulo en oraciones con LorcaJS. -// Cada oración es analizada... + Luego se procesa el articulo. Se extrae el HTML con cheerio. Se tokeniza el corpus del articulo en oraciones con LorcaJS. + Cada oración es analizada... -// Para analizar si el texto es confiable para leer vamos a analizar si las oraciones del texto presentan los sgts atributos deseables -// A1 = Objetividad - //A1a = Ausencia de oraciones subjetivas. (Uso de primera persona, adjetivos calificativos, verbos de opinión) - // Para esto se usa un clasificador de Bayes con un corpus de oraciones en noticas Argentinas labeleado - // de oraciones objetivas/subjetivas con LorcaJS. -// A2 = Accesibilidad - // A2a = Indice de lectura. Se usa un indicador built-in de LorcaJS que indica que tan sencillo es el articulo para leer. - // A2b = Presencia de lenguaje asertivo. - // Idem con lenguaje subjetivo. -// A3 = Verificabilidad - // A3a = Chequeabilidad. Se usa chequeabot de Chequeado. - // A3b = Cantidad de fuentes. Se usa una heuristica para detectar si el texto tiene fuentes o no. + Para analizar si el texto es confiable para leer vamos a analizar si las oraciones del texto presentan los sgts atributos deseables + A1 = Objetividad + A1a = Ausencia de oraciones subjetivas. (Uso de primera persona, adjetivos calificativos, verbos de opinión) + Para esto se usa un clasificador de Bayes con un corpus de oraciones en noticas Argentinas labeleado + de oraciones objetivas/subjetivas con LorcaJS. + A2 = Accesibilidad + A2a = Indice de lectura. Se usa un indicador built-in de LorcaJS que indica que tan sencillo es el articulo para leer. + A2b = Presencia de lenguaje asertivo. + Idem con lenguaje subjetivo. + A3 = Verificabilidad + A3a = Chequeabilidad. Se usa chequeabot de Chequeado. + A3b = Cantidad de fuentes. Se usa una heuristica para detectar si el texto tiene fuentes o no. -// A4 = Confiabilidad - // A4a = Presencia de oraciones argumentadas. Se entrena un clasificador de Bayes con un corpus de oraciones que expresan argumentos contra otras que asumen hechos sin justificación. - // A4b = Fecha de publicación del articulo > 2015 - // A4c = Independencia (Analiza si el medio pertenece a un grupo concentrado o no) - -// Cada atributo recibe un peso (weight) que representa su importancia. --> Se puede usar el metodo HPA para hacer este proceso. -// Por ahora se hizo de manera manual. -// Se rankea cada atributo y se computa un indice de decisión para el texto. + A4 = Confiabilidad + A4a = Presencia de oraciones argumentadas. Se entrena un clasificador de Bayes con un corpus de oraciones que expresan argumentos contra otras que asumen hechos sin justificación. + A4b = Fecha de publicación del articulo > 2015 + A4c = Independencia (Analiza si el medio pertenece a un grupo concentrado o no) -// Si un umbral predeterminado es superado, se recomienda leer el archivo. + Cada atributo recibe un peso (weight) que representa su importancia. --> Se puede usar el metodo HPA para hacer este proceso. + Por ahora se hizo de manera manual. + Se rankea cada atributo y se computa un indice de decisión para el texto. -// El agente formula en orden de prioridad 3 argumentos más relevantes para tomar esa decisión en base al ranking realizado de -// atributos. + Si un umbral predeterminado es superado, se recomienda leer el archivo. + El agente formula en orden de prioridad 3 argumentos más relevantes para tomar esa decisión en base al ranking realizado de + atributos. -// En caso que el texto no pase el umbral el agente recomienda otros sitios que hablen del mismo tema. - + En caso que el texto no pase el umbral el agente recomienda otros sitios que hablen del mismo tema. */ -////////////////////////////////// ENTRENAMIENTO de Clasificadores //////////////////////////// -//Objetividad +// //////////////////////////////// ENTRENAMIENTO de Clasificadores //////////////////////////// + +// Objetividad const objetividad = new natural.BayesClassifier(); // !! ATENCION -- SE NECESITAN MUCHISIMOS MAS CASOS @@ -72,11 +74,13 @@ objetividad.addDocument('mientras que las 383 comenzadas por trabajadores de cas objetividad.addDocument(' Las empresas más grandes fueron las que experimentaron una mayor merma en la cantidad de juicios enfrentados, al bajar un 44,5% en comparación con los del año pasado', 'objetivo'); objetividad.addDocument('El estudio destaca que de las cinco grandes provincias que tienen mayor cantidad de juicios contra el sistema iniciados por trabajadores de unidades productivas la mayor baja se registró en Córdoba (-80%)', 'objetivo'); objetividad.addDocument('En el mismo período, la cantidad de juicios registrados en la provincia de Buenos Aires subió un 23,7%', 'objetivo'); -objetividad.addDocument('', 'objetivo'); -objetividad.addDocument('', 'objetivo'); -objetividad.addDocument('', 'objetivo'); -objetividad.addDocument('', 'objetivo'); -objetividad.addDocument('', 'objetivo'); +objetividad.addDocument('El lunes, el Gobierno ratificó a través del Boletín Oficial el reajuste de los haberes para los jubilados. ', 'objetivo'); +objetividad.addDocument('El 4 de diciembre será el turno de la aplicación del 2x1 en las causa de delitos de lesa humanidad.', 'objetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR ARTICULOS', 'objetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR ARTICULOS', 'objetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR ARTICULOS', 'objetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR ARTICULOS', 'objetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR ARTICULOS', 'objetivo'); objetividad.addDocument('Las mujeres y su difícil relación con los hombres.', 'subjetivo'); objetividad.addDocument('O los hombres y su dificilísima relación con las mujeres. ', 'subjetivo'); objetividad.addDocument('me parece mentira lo mucho que están cambiado las cosas', 'subjetivo'); @@ -101,9 +105,12 @@ objetividad.addDocument('En ese momento trabajaba como repositor en un supermerc objetividad.addDocument('Copa América de Talla Baja: el espectacular gol de chilena con el que Argentina venció a Brasil en semifinales', 'subjetivo'); objetividad.addDocument('se impusieron 2-1 con una espectacular anotación de Martín Antúnez', 'subjetivo'); objetividad.addDocument('No fue todo: el primer gol de Antúnez fue una verdadera obra de arte. ', 'subjetivo'); -objetividad.addDocument('', 'subjetivo'); -objetividad.addDocument('', 'subjetivo'); -objetividad.addDocument('', 'subjetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR', 'subjetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR', 'subjetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR', 'subjetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR', 'subjetivo'); +objetividad.addDocument('AYUDAME A CLASIFICAR', 'subjetivo'); + objetividad.train(); @@ -114,13 +121,13 @@ const asertividad = new natural.BayesClassifier(); asertividad.addDocument('Mire, con tranquilidad podremos solucionar esto, no obstante, sabe que esto requiere de un tiempo para poder hacerlo por lo que podemos valorar si le merece la pena continuar ahora o en otro momento. ¿Le parece bien?', 'asertivo'); asertividad.addDocument('Tiene razón al decir que le hemos pedido estos datos en varias ocasiones pero entenderá que tengo que comprobar que todo es correcto.', 'asertivo'); asertividad.addDocument('¿Qué cree que podríamos hacer para que esto no volviera a ocurrir?', 'asertivo'); -asertividad.addDocument('', 'asertivo'); -asertividad.addDocument('', 'agresivo'); -asertividad.addDocument('', 'pasivo'); +asertividad.addDocument('AYUDAME A CLASIFICAR', 'asertivo'); +asertividad.addDocument('AYUDAME A CLASIFICAR', 'agresivo'); +asertividad.addDocument('AYUDAME A CLASIFICAR', 'pasivo'); asertividad.train(); -//VERIFICABILIDAD ---> ESTO VA A SER REEMPLAZADO CON CHEQUEABOT +// VERIFICABILIDAD ---> ESTO VA A SER REEMPLAZADO CON CHEQUEABOT const verificabilidad = new natural.BayesClassifier(); // !! ATENCION -- SE NECESITAN MUCHISIMOS MAS CASOS @@ -129,18 +136,18 @@ verificabilidad.addDocument('Los desembolsos más grandes llegan en diciembre y verificabilidad.addDocument('El total es de 53.600 millones de dólares.', 'verificable'); verificabilidad.addDocument('El Directorio Ejecutivo del Fondo Monetario Internacional (FMI) aprobó ayer en Washington el renovado acuerdo stand by que otorgará un total de 56.300 millones de dólares para la Argentina.', 'verificable'); verificabilidad.addDocument('El estudio destaca que de las cinco grandes provincias que tienen mayor cantidad de juicios contra el sistema iniciados por trabajadores de unidades productivas la mayor baja se registró en Córdoba (-80%)', 'verificable'); -verificabilidad.addDocument('', 'verificable'); -verificabilidad.addDocument('', 'verificable'); -verificabilidad.addDocument('', 'verificable'); -verificabilidad.addDocument('', 'verificable'); -verificabilidad.addDocument('', 'verificable'); -verificabilidad.addDocument('', 'verificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'verificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'verificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'verificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'verificable'); verificabilidad.addDocument('Trabajar la asertividad en la comunicación es una de las habilidades deseables para cualquier trabajador', 'noVerificable'); verificabilidad.addDocument('La comunicación de tipo asertivo es la forma más adecuada para dirigirnos a un cliente, ya que es la mejor manera de expresar lo que queremos decir sin que el otro interlocutor se sienta agredido', 'noVerificable'); -verificabilidad.addDocument('', 'noVerificable'); -verificabilidad.addDocument('', 'noVerificable'); -verificabilidad.addDocument('', 'noVerificable'); -verificabilidad.addDocument('', 'noVerificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'noVerificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'noVerificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'noVerificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'noVerificable'); +verificabilidad.addDocument('AYUDAME A CLASIFICAR', 'noVerificable'); + verificabilidad.train(); @@ -157,19 +164,19 @@ argumentatividad.addDocument('Un estudio reciente elaborado por el Departamento argumentatividad.addDocument('El estudio destaca que de las cinco grandes provincias que tienen mayor cantidad de juicios contra el sistema iniciados por trabajadores de unidades productivas la mayor baja se registró en Córdoba (-80%)', 'argumentado'); argumentatividad.addDocument('Entre las jurisdicciones que marcaron subas, el informe menciona a Santa Fe, que aún no se adhirió a la nueva ley de riesgos del trabajo, donde la litigiosidad subió un 3,6%.', 'argumentado'); argumentatividad.addDocument(' el informe nota que los servicios financieros observaron la mayor caída en litigios, por un 45,8%', 'argumentado'); -argumentatividad.addDocument('', 'argumentado'); -argumentatividad.addDocument('', 'argumentado'); -argumentatividad.addDocument('', 'argumentado'); -argumentatividad.addDocument('', 'argumentado'); -argumentatividad.addDocument('', 'argumentado'); -argumentatividad.addDocument('', 'argumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'argumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'argumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'argumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'argumentado'); argumentatividad.addDocument('Las mujeres y su difícil relación con los hombres.', 'desargumentado'); argumentatividad.addDocument('O los hombres y su dificilísima relación con las mujeres.', 'desargumentado'); argumentatividad.addDocument('Trabajar la asertividad en la comunicación es una de las habilidades deseables para cualquier trabajadorTrabajar la asertividad en la comunicación es una de las habilidades deseables para cualquier trabajador', 'desargumentado'); -argumentatividad.addDocument('', 'desargumentado'); -argumentatividad.addDocument('', 'desargumentado'); -argumentatividad.addDocument('', 'desargumentado'); -argumentatividad.addDocument('', 'desargumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'desargumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'desargumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'desargumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'desargumentado'); +argumentatividad.addDocument('AYUDAME A CLASIFICAR', 'desargumentado'); + argumentatividad.train(); @@ -203,13 +210,11 @@ csv() } } }); - - - - // SimbiaticJS !! BETA !! - // Por ahora usamos un paradigma basado en objetos, pero luego usaremos metodos proveenientes de un agente. - // Creamos el prototipo de agente que usaremos en versiones posteriores de esta aplicación. + +// SimbiaticJS !! BETA !! +// Por ahora usamos un paradigma basado en objetos, pero luego usaremos metodos proveenientes de un agente. +// Creamos el prototipo de agente que usaremos en versiones posteriores de esta aplicación. const agent = function (name) { this.name = name; }; @@ -234,7 +239,6 @@ router.get('/', (req, res, next) => { }); - // Este es el endpoint para filtrar una noticia. router.post('/filter', (req, res, next) => { @@ -250,9 +254,9 @@ router.post('/filter', (req, res, next) => { mediaIndex: 0, nombre: '', dueno: '', - financiamiento: 'No encontrado', - monetizacion: 'No encontrado', - esGrupoConcentrado: 'Sí', + financiamiento: 'No encontrado', + monetizacion: 'No encontrado', + esGrupoConcentrado: 'Sí', tema: '', temas_relacionados: [], fuentes: [], @@ -261,7 +265,7 @@ router.post('/filter', (req, res, next) => { verificabilidad: 0, accesibilidad: 0, confiabilidad: 0, - puntaje: 0, + puntaje: 0, texto: '', }; @@ -281,9 +285,9 @@ router.post('/filter', (req, res, next) => { if (response.nombre === relaciones[i].NOMBRE) { response.dueno = relaciones[i].ENTIDAD; response.porcentaje = relaciones[i].PORCENTAJE; - //response.financiamiento = relaciones[i].FINANCIAMIENTO; - //response.monetizacion = relaciones[i].MONETIZACION; - //response.esGrupoConcentrado = relaciones[i].GRUPOCONCENTRADO; + // response.financiamiento = relaciones[i].FINANCIAMIENTO; + // response.monetizacion = relaciones[i].MONETIZACION; + // response.esGrupoConcentrado = relaciones[i].GRUPOCONCENTRADO; } } @@ -301,16 +305,16 @@ router.post('/filter', (req, res, next) => { // body = striptags(body, [], ' '); body = $('body').text(); - + // SACAMOS LA INFORMACION QUE PODEMOS DE LOS METATAGS DE OPENGRAPH response.imagethum = $('meta[property="og:image"]').attr('content'); response.titulo = $('meta[property="og:title"]').attr('content'); response.descripcion = $('meta[property="og:description"]').attr('content'); - + // USAMOS LORCA PARA ANALIZAR EL TEXTO LINGUISTICAMENTE const doc = lorca(body); - - + + // Para detectar el posible tema del texto , agarramos todo el texto y le sacamos las stopwords... let nText = String(body).toLowerCase(); const stopWords = [response.nombre, ' al ', ' no ', ' si ', ' su ', 'qué', 'más', ' uno ', ' como ', ' con ', 'La ', 'El ', 'Lo ', ' son ', 'Los ', 'No ', ' las ', ' sus ', 'Su ', ' con ', 'Te ', 'Para ', ' yo ', ' el ', ' se ', ' por ', ' vos ', ' un ', ' de ', ' tu ', ' para ', ' el ', ' lo ', ' los ', ' ella ', ' de ', ' es ', ' una ', ' fue ', ' tiene ', ' la ', ' y ', ' del ', ' los ', ' que ', ' a ', ' en ', ' el ']; @@ -340,11 +344,11 @@ router.post('/filter', (req, res, next) => { let objectivity = 0; let argumentativity = 0; let verificability = 0; - let accesibility = 0; - let assertiveness = 0; + const accesibility = 0; + let assertiveness = 0; let powerConcentration = 0; - - + + // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. for (var i = 0; i != doc.sentences().get().length; i++) { if (verificabilidad.classify(doc.sentences().get()[i]) === 'verificable') { @@ -356,64 +360,63 @@ router.post('/filter', (req, res, next) => { if (argumentatividad.classify(doc.sentences().get()[i]) === 'argumentado') { argumentativity += 1; } - + if (asertividad.classify(doc.sentences().get()[i]) === 'asertivo') { assertiveness += 1; } - - // USAMOS ESTA HEURISTICA PARA VER SI EL TEXTO CONTIENE FUENTES. + + // USAMOS ESTA HEURISTICA PARA VER SI EL TEXTO CONTIENE FUENTES. if (String(doc.sentences().get()[i]).includes('Fuente') === true) { console.log(String(doc.sentences().get()[i])); response.fuentes.push(String(doc.sentences().get()[i])); } } - + // Computamos objetividad... - // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. - response.objetividad = Math.round(objectivity / doc.sentences().get().length ) * 10; + // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. + response.objetividad = Math.round(objectivity / doc.sentences().get().length) * 10; // Computamos accesibilidad... - //Facilidad de lectura (LorcaJS) en un indice del 1 al 10 - let readability = Math.round((doc.ifsz().get() / 100)) * 10; - - // Computamos asertvidad... (Cantidad de oraciones con lenguaje asertivo / total oraciones en un indice de 1 a 10) - asertiveness = (Math.round(assertiveness / doc.sentences().get().length * 100) / 100) * 10 - + // Facilidad de lectura (LorcaJS) en un indice del 1 al 10 + const readability = Math.round((doc.ifsz().get() / 100)) * 10; + + // Computamos asertvidad... (Cantidad de oraciones con lenguaje asertivo / total oraciones en un indice de 1 a 10) + asertiveness = (Math.round(assertiveness / doc.sentences().get().length * 100) / 100) * 10; + // Se multiplica cada subatributo por un peso y se divide por dos. - response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); - + response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); - - // Computamos verificabilidad + + // Computamos verificabilidad verificability = (Math.round(verificability / doc.sentences().get().length * 100) / 100) * 10; - // (Cantidad de fuentes. (Máx 5) / 5) en un index 1/10 - let sources = 0; - if (response.fuentes.length > 5) { - sources = 10; - } else { - sources = (response.fuentes.length / 5 ) * 10; - } - response.verificabilidad = Math.round((verificability * 0.8 + sources * 0.2) / 2); - - - //Computamos confiabilidad... - - // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 + // (Cantidad de fuentes. (Máx 5) / 5) en un index 1/10 + let sources = 0; + if (response.fuentes.length > 5) { + sources = 10; + } else { + sources = (response.fuentes.length / 5) * 10; + } + response.verificabilidad = Math.round((verificability * 0.8 + sources * 0.2) / 2); + + + // Computamos confiabilidad... + + // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 argumentativity = (Math.round(argumentativity / doc.sentences().get().length * 100) / 100) * 10; - + // Aca no tenemos forma por ahora de sacar la fecha de publicación pero lo solucionaremos en versiones posteriores. - + if (response.esGrupoConcentrado !== 'Sí') { powerConcentration = 0; } else { powerConcentration = 10; } - + response.confiabilidad = Math.round((argumentativity * 0.95 + powerConcentration * 0.05) / 2); - // Ahora que computamos nuestros atributos con sus subtributos vamos a crear una matriz de decisión representada en + // Ahora que computamos nuestros atributos con sus subtributos vamos a crear una matriz de decisión representada en // este JSON Array. const criterias = [ @@ -422,63 +425,88 @@ router.post('/filter', (req, res, next) => { argument: 'en general, el texto contiene información y datos objetivos', score: response.objetividad, weight: 0.40, - rating: response.objetividad * 0.4, + rating: null, rank: 0, }, { name: 'Accesibilidad', argument: 'usa una terminologia sencilla, lo que sugiere que se haga facíl de leer', score: response.accesibilidad, weight: 0.10, - rating: response.accesibilidad * 0.10, + rating: null, rank: 0, }, { name: 'Verificabilidad', argument: 'el contenido generalmente es chequeable', score: response.verificabilidad, weight: 0.30, - rating: response.verificabilidad * 0.3, + rating: null, rank: 0, }, { name: 'Confibilidad', argument: 'observé que el autor suele articular sus ideas con argumentos', score: response.accesibilidad, weight: 0.20, - rating: response.accesibilidad * 0.20, + rating: null, rank: 0, }, ]; + // Calculamos el vector del puntaje con el pesado correspondiente... + criterias[0].rating = criterias[0].score * criterias[0].weight; + criterias[1].rating = criterias[1].score * criterias[1].weight; + criterias[2].rating = criterias[2].score * criterias[2].weight; + criterias[3].rating = criterias[3].score * criterias[3].weight; + + // Tomamos el puntaje obtenido. + const actualScore = [criterias[0].rating, criterias[1].rating, criterias[2].rating, criterias[3].rating]; + + // Definimos el mejor escenario posible... + const idealScore = [10 * 0.4, 10 * 0.1, 10 * 0.3, 10 * 0.2]; + + // Definimos el peor escenario posible... + const worstScore = [0, 0, 0, 0]; + + // ALGORITMO DE TOPSIS + + // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. + const distP = distance(actualScore, idealScore); + + // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. + const distN = distance(actualScore, worstScore); + + // Calculando similitud al peor escenario... + const sim = distN / (distN + distP); + + console.log(sim); - // Calculamos puntaje final. + // Calculamos puntaje final. const score = criterias[0].rating + criterias[1].rating + criterias[2].rating + criterias[3].rating; - - response.puntaje = score; - + + response.puntaje = Math.round(sim * 10); + // Chequeamos si no pasamos el criterio para negativizar argumentos... - if (score < 7) { + if (sim < 0.65) { criterias[0].argument = 'en general, el texto abusa de la opinión y el lenguaje subjetivo'; criterias[1].argument = 'usa una terminologia complejos son solo pueden ser entendidos por expertos'; criterias[2].argument = 'el contenido de este texto es dificil de chequear'; criterias[3].argument = 'observé que el autor suele asumir hechos y expresar su posición sin justificarse'; } - - - //Computamos tiempo de lectura que usaremos en la rta del agente.. - let tiempoLectura = Math.round(doc.readingTime()); - + + // Computamos tiempo de lectura que usaremos en la rta del agente.. + const tiempoLectura = Math.round(doc.readingTime()); + // Si pasa umbral.. - if (score > 7) { - // Vamos a rankear cada argumento de mayor a menor... + if (sim > 0.65) { + // Vamos a rankear cada argumento de mayor a menor... criterias.sort((a, b) => parseFloat(b.rating) - parseFloat(a.rating)); - response.texto = `Analicé el articulo de ${response.nombre} sobre ${response.tema[0]} y ${response.tema[1]}. Te recomiendo leerlo ya que ` + `(A) ${criterias[0].argument} , (B) ${criterias[1].argument} y (C) ${criterias[2].argument}. Espero que te interese, te va a llevar unos ${tiempoLectura} minutos leerlo. Gracias por confiar en mi. `; + response.texto = `Analicé el articulo de ${response.nombre} sobre ${response.tema[0]} y ${response.tema[1]}. Te recomiendo leerlo ya que ` + `(A) ${criterias[0].argument} , (B) ${criterias[1].argument} y (C) ${criterias[2].argument}. Espero que te interese, te va a llevar unos ${tiempoLectura} minutos leerlo. Gracias por confiar en mi. `; } else { - //Sino... + // Sino... // Vamos a rankear cada argumento de menor a mayor... criterias.sort((a, b) => parseFloat(a.rating) - parseFloat(b.rating)); - response.texto = `Analicé el articulo de ${response.nombre} sobre ${response.tema[0]} y ${response.tema[1]}. No te recomiendo leerlo ya que ` + `(A) ${criterias[0].argument} , (B) ${criterias[1].argument} y (C) ${criterias[2].argument}. En su lugar podés buscar en google por otras fuentes acerca del mismo tiempo. Gracias por confiar en mi.`; - + response.texto = `Analicé el articulo de ${response.nombre} sobre ${response.tema[0]} y ${response.tema[1]}. No te recomiendo leerlo ya que ` + `(A) ${criterias[0].argument} , (B) ${criterias[1].argument} y (C) ${criterias[2].argument}. En su lugar podés buscar en google por otras fuentes acerca del mismo tiempo. Gracias por confiar en mi.`; } console.log(response); @@ -488,17 +516,9 @@ router.post('/filter', (req, res, next) => { title: 'Resultado del filtro', result: response, }); - - - }); }); // END request request - - - }); // END ROUTE - - module.exports = router; From 5a70be90ecd5a600086e08bfecbc56fff0c51851 Mon Sep 17 00:00:00 2001 From: Patricio Julian Date: Thu, 15 Nov 2018 13:18:48 +0000 Subject: [PATCH 02/10] add reglas rebatibles --- routes/index.js | 81 ++++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/routes/index.js b/routes/index.js index 4c0dc60..a79e148 100644 --- a/routes/index.js +++ b/routes/index.js @@ -300,23 +300,23 @@ router.post('/filter', (req, res, next) => { body += data; }); resp.on('end', () => { - // USAMOS CHEERIO PARA OBTENER EL TEXTO SIN TAGS DE HTML + // USAMOS CHEERIO PARA OBTENER EL TEXTO SIN TAGS DE HTML const $ = cheerio.load(String(body)); // body = striptags(body, [], ' '); body = $('body').text(); - // SACAMOS LA INFORMACION QUE PODEMOS DE LOS METATAGS DE OPENGRAPH + // SACAMOS LA INFORMACION QUE PODEMOS DE LOS METATAGS DE OPENGRAPH response.imagethum = $('meta[property="og:image"]').attr('content'); response.titulo = $('meta[property="og:title"]').attr('content'); response.descripcion = $('meta[property="og:description"]').attr('content'); - - // USAMOS LORCA PARA ANALIZAR EL TEXTO LINGUISTICAMENTE - const doc = lorca(body); + + // USAMOS LORCA PARA ANALIZAR EL TEXTO LINGUISTICAMENTE + const doc = lorca(body); // Para detectar el posible tema del texto , agarramos todo el texto y le sacamos las stopwords... - let nText = String(body).toLowerCase(); + let nText = String(body).toLowerCase(); const stopWords = [response.nombre, ' al ', ' no ', ' si ', ' su ', 'qué', 'más', ' uno ', ' como ', ' con ', 'La ', 'El ', 'Lo ', ' son ', 'Los ', 'No ', ' las ', ' sus ', 'Su ', ' con ', 'Te ', 'Para ', ' yo ', ' el ', ' se ', ' por ', ' vos ', ' un ', ' de ', ' tu ', ' para ', ' el ', ' lo ', ' los ', ' ella ', ' de ', ' es ', ' una ', ' fue ', ' tiene ', ' la ', ' y ', ' del ', ' los ', ' que ', ' a ', ' en ', ' el ']; @@ -327,7 +327,7 @@ router.post('/filter', (req, res, next) => { // Usamos lorca para manipular la linguistica del texto nText = lorca(String(nText)); - // El metodo concordance nos dice cual es las palabras más repetidas... sacamos las dos primeras que se repitan que no sean stopwords. + // El metodo concordance nos dice cual es las palabras más repetidas... sacamos las dos primeras que se repitan que no sean stopwords. nText = nText.concordance().sort(2).get(); response.tema = Object.keys(nText).map(key => [String(key), nText[key]]); @@ -340,16 +340,16 @@ router.post('/filter', (req, res, next) => { response.tema = fixArray; - // AHORA vamos a computar los atributos de nuestra decisión en base a lo que encontremos en el texto... + // AHORA vamos a computar los atributos de nuestra decisión en base a lo que encontremos en el texto... let objectivity = 0; let argumentativity = 0; - let verificability = 0; - const accesibility = 0; - let assertiveness = 0; - let powerConcentration = 0; + let verificability = 0; + let accesibility = 0; + let assertiveness = 0; + let powerConcentration = 0; - // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. + // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. for (var i = 0; i != doc.sentences().get().length; i++) { if (verificabilidad.classify(doc.sentences().get()[i]) === 'verificable') { verificability += 1; @@ -373,19 +373,19 @@ router.post('/filter', (req, res, next) => { } - // Computamos objetividad... - // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. - response.objetividad = Math.round(objectivity / doc.sentences().get().length) * 10; + // Computamos objetividad... + // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. + response.objetividad = Math.round(objectivity / doc.sentences().get().length) * 10; - // Computamos accesibilidad... + // Computamos accesibilidad... // Facilidad de lectura (LorcaJS) en un indice del 1 al 10 const readability = Math.round((doc.ifsz().get() / 100)) * 10; // Computamos asertvidad... (Cantidad de oraciones con lenguaje asertivo / total oraciones en un indice de 1 a 10) asertiveness = (Math.round(assertiveness / doc.sentences().get().length * 100) / 100) * 10; - // Se multiplica cada subatributo por un peso y se divide por dos. - response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); + // Se multiplica cada subatributo por un peso y se divide por dos. + response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); // Computamos verificabilidad @@ -395,14 +395,14 @@ router.post('/filter', (req, res, next) => { if (response.fuentes.length > 5) { sources = 10; } else { - sources = (response.fuentes.length / 5) * 10; + sources = (response.fuentes.length / 5) * 10; } response.verificabilidad = Math.round((verificability * 0.8 + sources * 0.2) / 2); // Computamos confiabilidad... - // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 + // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 argumentativity = (Math.round(argumentativity / doc.sentences().get().length * 100) / 100) * 10; // Aca no tenemos forma por ahora de sacar la fecha de publicación pero lo solucionaremos en versiones posteriores. @@ -416,8 +416,17 @@ router.post('/filter', (req, res, next) => { response.confiabilidad = Math.round((argumentativity * 0.95 + powerConcentration * 0.05) / 2); - // Ahora que computamos nuestros atributos con sus subtributos vamos a crear una matriz de decisión representada en - // este JSON Array. + // Ahora que computamos nuestros atributos con sus subtributos vamos a crear una matriz de decisión representada en + // este JSON Array. + + // El agente desarrolla argumentos a favor u en contra en base a las siguientes reglas rebatibles: + + /* + + 1. Textos con presencia de lenguaje objetivo, vocabulario sencilloo, contenido chequeable y afirmaciones argumentadas usualmente son confiables de leer. + 2. Si texto N posee dichas caracteristicas creo que el texto es confiable, hasta que algo o alguien me desmuestre lo contrario. + + */ const criterias = [ { @@ -450,7 +459,7 @@ router.post('/filter', (req, res, next) => { rank: 0, }, - ]; + ]; // Calculamos el vector del puntaje con el pesado correspondiente... criterias[0].rating = criterias[0].score * criterias[0].weight; @@ -458,18 +467,18 @@ router.post('/filter', (req, res, next) => { criterias[2].rating = criterias[2].score * criterias[2].weight; criterias[3].rating = criterias[3].score * criterias[3].weight; - // Tomamos el puntaje obtenido. - const actualScore = [criterias[0].rating, criterias[1].rating, criterias[2].rating, criterias[3].rating]; + // Tomamos el puntaje obtenido. + const actualScore = [criterias[0].rating, criterias[1].rating, criterias[2].rating, criterias[3].rating]; - // Definimos el mejor escenario posible... - const idealScore = [10 * 0.4, 10 * 0.1, 10 * 0.3, 10 * 0.2]; + // Definimos el mejor escenario posible... + const idealScore = [10 * 0.4, 10 * 0.1, 10 * 0.3, 10 * 0.2]; - // Definimos el peor escenario posible... - const worstScore = [0, 0, 0, 0]; + // Definimos el peor escenario posible... + const worstScore = [0, 0, 0, 0]; // ALGORITMO DE TOPSIS - // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. + // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. const distP = distance(actualScore, idealScore); // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. @@ -480,7 +489,7 @@ router.post('/filter', (req, res, next) => { console.log(sim); - // Calculamos puntaje final. + // Calculamos puntaje final. const score = criterias[0].rating + criterias[1].rating + criterias[2].rating + criterias[3].rating; response.puntaje = Math.round(sim * 10); @@ -494,10 +503,12 @@ router.post('/filter', (req, res, next) => { } - // Computamos tiempo de lectura que usaremos en la rta del agente.. - const tiempoLectura = Math.round(doc.readingTime()); + // Computamos tiempo de lectura que usaremos en la rta del agente.. + const tiempoLectura = Math.round(doc.readingTime()); - // Si pasa umbral.. + // Regla rebatible: Usualmente los textos con presencia de caracteristicas A,B,C son confiables de leer. + // Texto tiene / no tiene caracteristicas A,B,C entonces creo que el Texto es ... + // ya que posee / no posee A,B,C... if (sim > 0.65) { // Vamos a rankear cada argumento de mayor a menor... criterias.sort((a, b) => parseFloat(b.rating) - parseFloat(a.rating)); From 49b546a4005a4955916187a71ec883942df8d459 Mon Sep 17 00:00:00 2001 From: Patricio Julian Date: Thu, 15 Nov 2018 20:31:29 +0000 Subject: [PATCH 03/10] Update readme.md --- readme.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index b59385b..f35753a 100644 --- a/readme.md +++ b/readme.md @@ -156,8 +156,8 @@ Route: **/filter** ```javascript PostBody = { - url: - }; + url: +}; ``` ```javascript @@ -196,7 +196,24 @@ response = { * Es una versión BETA no libre de bugs. * La voz se sintetiza con un motor gratuito de voz libre, esta pensado para el italiano. Para un TTS en español hay que integrar con una solución de cloud que tenga soporte en ese idioma. -## Referencias y bibliografía: +## Referencias y bibliografía de soporte: +Edward S.. Herman, & Chomsky, N. (1988). Manufacturing consent: The political economy of the mass media. London: Vintage. + +### NPM packages documentation +* LorcaJS: https://github.com/dmarman/lorca + +### AHP (Algorithm: Analytic Hierarchy proccess) +* Saaty, T. L. (1986). Axiomatic Foundation of the Analytic Hierarchy Process. Management Science, 32(7), 841. doi:10.1287/mnsc.32.7.841 +* Saaty, R. W. (1987). The analytic hierarchy process—what it is and how it is used. Mathematical Modelling, 9(3-5), 167. doi:10.1016/0270-0255(87)90473-8 +* Manoj Mathew (2018). Analytic Hierarchy Process (AHP). Youtube. Retrieved from https://www.youtube.com/watch?v=J4T70o8gjlk + +### TOPSIS (Algorithm: Technique for Order of Preference by Similarity to Ideal Solution) +* Hwang, C.L.; Yoon, K. (1981). Multiple Attribute Decision Making: Methods and Applications. New York: Springer-Verlag. +* Hwang, C.L.; Lai, Y.J.; Liu, T.Y. (1993). "A new approach for multiple objective decision making". Computers and Operational Research. 20: 889–899 * Değer Alper, Canan Başdar (2017). A Comparison of TOPSIS and ELECTRE Methods: An Application on the Factoring Industry. * Freire, S. M., Nascimento, A., & de Almeida, R. T. (2018). A Multiple Criteria Decision Making System for Setting Priorities. World Congress on Medical Physics and Biomedical Engineering 2018, 357–361. doi:10.1007/978-981-10-9035-6_65 +* Mariana Arburua (2017). Método de Decisión Multicriterio: TOPSIS. Youtube. Retrieved from https://www.youtube.com/watch?v=p8WyEn14Cto + +### Razonamiento rebatible en problemas de decisión multi-criterio +* Ferretti, E., Errecalde, M., García, A. J., & Simari, G. R. (2007, May). An application of defeasible logic programming to decision making in a robotic environment. In International Conference on Logic Programming and Nonmonotonic Reasoning (pp. 297-302). Springer, Berlin, Heidelberg. From 254efc971c60cd1099fc61d3c5097c35eb4a6323 Mon Sep 17 00:00:00 2001 From: Patricio Julian Date: Thu, 15 Nov 2018 20:38:42 +0000 Subject: [PATCH 04/10] Update readme.md --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f35753a..5e5f789 100644 --- a/readme.md +++ b/readme.md @@ -198,7 +198,8 @@ response = { ## Referencias y bibliografía de soporte: -Edward S.. Herman, & Chomsky, N. (1988). Manufacturing consent: The political economy of the mass media. London: Vintage. +* Edward S.. Herman, & Chomsky, N. (1988). Manufacturing consent: The political economy of the mass media. London: Vintage. +* Al Jazeera English (2017). Noam Chomsky - The 5 Filters of the Mass Media Machine. Youtube. Retrieved from https://www.youtube.com/watch?v=34LGPIXvU5M ### NPM packages documentation * LorcaJS: https://github.com/dmarman/lorca From 6fec8a6713734da186c4654a1c65b42aa67508c3 Mon Sep 17 00:00:00 2001 From: Patricio Julian Date: Fri, 16 Nov 2018 15:23:27 +0000 Subject: [PATCH 05/10] Update index.js --- routes/index.js | 143 ++++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/routes/index.js b/routes/index.js index a79e148..ab5812a 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,29 +1,14 @@ -const express = require('express'); - -const router = express.Router(); - -// Librerias para extraer HTML de los articulos -const https = require('https'); -const cheerio = require('cheerio'); -// Preprocesamiento - Elimina los tags de html del string extraido. -const striptags = require('striptags'); +/***************************************************************************************************************** +****************************************************************************************************************** + index - index.js +****************************************************************************************************************** +****************************************************************************************************************** +Este archivo contiene la lógica de nuestro agente de soporte de decisión. -// Libreria para usar timestamps en NodeJS. -const moment = require('moment'); +****************************************************************************************************************** -// Herramientas de matematica y algoritmica. -const math = require('mathjs'); -const compare = require('hamming-distance'); -const hamming = require('compute-hamming'); -let distance = require('euclidean-distance'); - -// Herramientas de NLP -const natural = require('natural'); -const lorca = require('lorca-nlp'); - - -/* Vamos a partir de una URL de una noticia. +Vamos a partir de una URL de una noticia. Usamos el dataset de FOPEA de mapas de medios para indicar: Si el articulo viene de un medio que es parte de un grupo concentrado de medios y cuál. Forma de financiamiento y monetización del medio. @@ -59,10 +44,37 @@ const lorca = require('lorca-nlp'); atributos. - En caso que el texto no pase el umbral el agente recomienda otros sitios que hablen del mismo tema. */ + En caso que el texto no pase el umbral el agente recomienda otros sitios que hablen del mismo tema. + +****************************************************************************************************************** +*****************************************************************************************************************/ +const express = require('express'); -// //////////////////////////////// ENTRENAMIENTO de Clasificadores //////////////////////////// +const router = express.Router(); + +// Librerias para extraer HTML de los articulos +const https = require('https'); +const cheerio = require('cheerio'); + +// Libreria para usar timestamps en NodeJS. +const moment = require('moment'); + + +// Herramientas de matematica y algoritmica. +const math = require('mathjs'); +const compare = require('hamming-distance'); +const hamming = require('compute-hamming'); +let distance = require('euclidean-distance'); + +// Herramientas de NLP +const natural = require('natural'); +const lorca = require('lorca-nlp'); + + +/***************************************************************************************************************** + Entrenamiento de clasificadores +******************************************************************************************************************/ // Objetividad const objetividad = new natural.BayesClassifier(); @@ -300,23 +312,23 @@ router.post('/filter', (req, res, next) => { body += data; }); resp.on('end', () => { - // USAMOS CHEERIO PARA OBTENER EL TEXTO SIN TAGS DE HTML + // USAMOS CHEERIO PARA OBTENER EL TEXTO SIN TAGS DE HTML const $ = cheerio.load(String(body)); // body = striptags(body, [], ' '); body = $('body').text(); - // SACAMOS LA INFORMACION QUE PODEMOS DE LOS METATAGS DE OPENGRAPH + // SACAMOS LA INFORMACION QUE PODEMOS DE LOS METATAGS DE OPENGRAPH response.imagethum = $('meta[property="og:image"]').attr('content'); response.titulo = $('meta[property="og:title"]').attr('content'); response.descripcion = $('meta[property="og:description"]').attr('content'); - - // USAMOS LORCA PARA ANALIZAR EL TEXTO LINGUISTICAMENTE - const doc = lorca(body); + + // USAMOS LORCA PARA ANALIZAR EL TEXTO LINGUISTICAMENTE + const doc = lorca(body); // Para detectar el posible tema del texto , agarramos todo el texto y le sacamos las stopwords... - let nText = String(body).toLowerCase(); + let nText = String(body).toLowerCase(); const stopWords = [response.nombre, ' al ', ' no ', ' si ', ' su ', 'qué', 'más', ' uno ', ' como ', ' con ', 'La ', 'El ', 'Lo ', ' son ', 'Los ', 'No ', ' las ', ' sus ', 'Su ', ' con ', 'Te ', 'Para ', ' yo ', ' el ', ' se ', ' por ', ' vos ', ' un ', ' de ', ' tu ', ' para ', ' el ', ' lo ', ' los ', ' ella ', ' de ', ' es ', ' una ', ' fue ', ' tiene ', ' la ', ' y ', ' del ', ' los ', ' que ', ' a ', ' en ', ' el ']; @@ -327,7 +339,7 @@ router.post('/filter', (req, res, next) => { // Usamos lorca para manipular la linguistica del texto nText = lorca(String(nText)); - // El metodo concordance nos dice cual es las palabras más repetidas... sacamos las dos primeras que se repitan que no sean stopwords. + // El metodo concordance nos dice cual es las palabras más repetidas... sacamos las dos primeras que se repitan que no sean stopwords. nText = nText.concordance().sort(2).get(); response.tema = Object.keys(nText).map(key => [String(key), nText[key]]); @@ -340,16 +352,16 @@ router.post('/filter', (req, res, next) => { response.tema = fixArray; - // AHORA vamos a computar los atributos de nuestra decisión en base a lo que encontremos en el texto... + // AHORA vamos a computar los atributos de nuestra decisión en base a lo que encontremos en el texto... let objectivity = 0; let argumentativity = 0; - let verificability = 0; - let accesibility = 0; - let assertiveness = 0; - let powerConcentration = 0; + let verificability = 0; + const accesibility = 0; + let assertiveness = 0; + let powerConcentration = 0; - // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. + // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. for (var i = 0; i != doc.sentences().get().length; i++) { if (verificabilidad.classify(doc.sentences().get()[i]) === 'verificable') { verificability += 1; @@ -373,19 +385,19 @@ router.post('/filter', (req, res, next) => { } - // Computamos objetividad... - // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. - response.objetividad = Math.round(objectivity / doc.sentences().get().length) * 10; + // Computamos objetividad... + // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. + response.objetividad = Math.round(objectivity / doc.sentences().get().length) * 10; - // Computamos accesibilidad... + // Computamos accesibilidad... // Facilidad de lectura (LorcaJS) en un indice del 1 al 10 const readability = Math.round((doc.ifsz().get() / 100)) * 10; // Computamos asertvidad... (Cantidad de oraciones con lenguaje asertivo / total oraciones en un indice de 1 a 10) asertiveness = (Math.round(assertiveness / doc.sentences().get().length * 100) / 100) * 10; - // Se multiplica cada subatributo por un peso y se divide por dos. - response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); + // Se multiplica cada subatributo por un peso y se divide por dos. + response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); // Computamos verificabilidad @@ -395,14 +407,14 @@ router.post('/filter', (req, res, next) => { if (response.fuentes.length > 5) { sources = 10; } else { - sources = (response.fuentes.length / 5) * 10; + sources = (response.fuentes.length / 5) * 10; } response.verificabilidad = Math.round((verificability * 0.8 + sources * 0.2) / 2); // Computamos confiabilidad... - // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 + // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 argumentativity = (Math.round(argumentativity / doc.sentences().get().length * 100) / 100) * 10; // Aca no tenemos forma por ahora de sacar la fecha de publicación pero lo solucionaremos en versiones posteriores. @@ -416,17 +428,8 @@ router.post('/filter', (req, res, next) => { response.confiabilidad = Math.round((argumentativity * 0.95 + powerConcentration * 0.05) / 2); - // Ahora que computamos nuestros atributos con sus subtributos vamos a crear una matriz de decisión representada en - // este JSON Array. - - // El agente desarrolla argumentos a favor u en contra en base a las siguientes reglas rebatibles: - - /* - - 1. Textos con presencia de lenguaje objetivo, vocabulario sencilloo, contenido chequeable y afirmaciones argumentadas usualmente son confiables de leer. - 2. Si texto N posee dichas caracteristicas creo que el texto es confiable, hasta que algo o alguien me desmuestre lo contrario. - - */ + // Ahora que computamos nuestros atributos con sus subtributos vamos a crear una matriz de decisión representada en + // este JSON Array. const criterias = [ { @@ -459,7 +462,7 @@ router.post('/filter', (req, res, next) => { rank: 0, }, - ]; + ]; // Calculamos el vector del puntaje con el pesado correspondiente... criterias[0].rating = criterias[0].score * criterias[0].weight; @@ -467,18 +470,18 @@ router.post('/filter', (req, res, next) => { criterias[2].rating = criterias[2].score * criterias[2].weight; criterias[3].rating = criterias[3].score * criterias[3].weight; - // Tomamos el puntaje obtenido. - const actualScore = [criterias[0].rating, criterias[1].rating, criterias[2].rating, criterias[3].rating]; + // Tomamos el puntaje obtenido. + const actualScore = [criterias[0].rating, criterias[1].rating, criterias[2].rating, criterias[3].rating]; - // Definimos el mejor escenario posible... - const idealScore = [10 * 0.4, 10 * 0.1, 10 * 0.3, 10 * 0.2]; + // Definimos el mejor escenario posible... + const idealScore = [10 * 0.4, 10 * 0.1, 10 * 0.3, 10 * 0.2]; - // Definimos el peor escenario posible... - const worstScore = [0, 0, 0, 0]; + // Definimos el peor escenario posible... + const worstScore = [0, 0, 0, 0]; // ALGORITMO DE TOPSIS - // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. + // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. const distP = distance(actualScore, idealScore); // Calculamos la distancia ecluidea del puntaje obtenido y el mejor escenario. @@ -489,7 +492,7 @@ router.post('/filter', (req, res, next) => { console.log(sim); - // Calculamos puntaje final. + // Calculamos puntaje final. const score = criterias[0].rating + criterias[1].rating + criterias[2].rating + criterias[3].rating; response.puntaje = Math.round(sim * 10); @@ -503,12 +506,10 @@ router.post('/filter', (req, res, next) => { } - // Computamos tiempo de lectura que usaremos en la rta del agente.. - const tiempoLectura = Math.round(doc.readingTime()); + // Computamos tiempo de lectura que usaremos en la rta del agente.. + const tiempoLectura = Math.round(doc.readingTime()); - // Regla rebatible: Usualmente los textos con presencia de caracteristicas A,B,C son confiables de leer. - // Texto tiene / no tiene caracteristicas A,B,C entonces creo que el Texto es ... - // ya que posee / no posee A,B,C... + // Si pasa umbral.. if (sim > 0.65) { // Vamos a rankear cada argumento de mayor a menor... criterias.sort((a, b) => parseFloat(b.rating) - parseFloat(a.rating)); From 11417f2cee688166dfd95c93441b037f1cd24402 Mon Sep 17 00:00:00 2001 From: Patelo Date: Sun, 25 Nov 2018 22:21:12 +0000 Subject: [PATCH 06/10] add fuzzyness --- routes/index.js | 79 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/routes/index.js b/routes/index.js index ab5812a..5afd742 100644 --- a/routes/index.js +++ b/routes/index.js @@ -352,7 +352,7 @@ router.post('/filter', (req, res, next) => { response.tema = fixArray; - // AHORA vamos a computar los atributos de nuestra decisión en base a lo que encontremos en el texto... + // AHORA vamos a computar los atributos de nuestra decisión en base a lo que encontremos en el texto... let objectivity = 0; let argumentativity = 0; let verificability = 0; @@ -361,7 +361,7 @@ router.post('/filter', (req, res, next) => { let powerConcentration = 0; - // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. + // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. for (var i = 0; i != doc.sentences().get().length; i++) { if (verificabilidad.classify(doc.sentences().get()[i]) === 'verificable') { verificability += 1; @@ -396,8 +396,8 @@ router.post('/filter', (req, res, next) => { // Computamos asertvidad... (Cantidad de oraciones con lenguaje asertivo / total oraciones en un indice de 1 a 10) asertiveness = (Math.round(assertiveness / doc.sentences().get().length * 100) / 100) * 10; - // Se multiplica cada subatributo por un peso y se divide por dos. - response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); + // Se multiplica cada subatributo por un peso y se divide por dos. + response.accesibilidad = Math.round((readability * 0.9 + asertiveness * 0.1) / 2); // Computamos verificabilidad @@ -407,14 +407,14 @@ router.post('/filter', (req, res, next) => { if (response.fuentes.length > 5) { sources = 10; } else { - sources = (response.fuentes.length / 5) * 10; + sources = (response.fuentes.length / 5) * 10; } response.verificabilidad = Math.round((verificability * 0.8 + sources * 0.2) / 2); // Computamos confiabilidad... - // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 + // Presencia de oraciones argumentadas / Total oraciones en un indice de 10 argumentativity = (Math.round(argumentativity / doc.sentences().get().length * 100) / 100) * 10; // Aca no tenemos forma por ahora de sacar la fecha de publicación pero lo solucionaremos en versiones posteriores. @@ -470,14 +470,14 @@ router.post('/filter', (req, res, next) => { criterias[2].rating = criterias[2].score * criterias[2].weight; criterias[3].rating = criterias[3].score * criterias[3].weight; - // Tomamos el puntaje obtenido. - const actualScore = [criterias[0].rating, criterias[1].rating, criterias[2].rating, criterias[3].rating]; + // Tomamos el puntaje obtenido. + const actualScore = [criterias[0].rating, criterias[1].rating, criterias[2].rating, criterias[3].rating]; - // Definimos el mejor escenario posible... - const idealScore = [10 * 0.4, 10 * 0.1, 10 * 0.3, 10 * 0.2]; + // Definimos el mejor escenario posible... + const idealScore = [10 * 0.4, 10 * 0.1, 10 * 0.3, 10 * 0.2]; - // Definimos el peor escenario posible... - const worstScore = [0, 0, 0, 0]; + // Definimos el peor escenario posible... + const worstScore = [0, 0, 0, 0]; // ALGORITMO DE TOPSIS @@ -492,25 +492,60 @@ router.post('/filter', (req, res, next) => { console.log(sim); - // Calculamos puntaje final. + // Calculamos puntaje final. const score = criterias[0].rating + criterias[1].rating + criterias[2].rating + criterias[3].rating; - response.puntaje = Math.round(sim * 10); - - // Chequeamos si no pasamos el criterio para negativizar argumentos... - if (sim < 0.65) { + response.puntaje = Math.round(sim * 100) / 100; + + // Creamos un fuzzy set para definir nuestro umbral de decisión + let fuzzySet = { + linguisticLabels: ['low', 'medium', 'high'], + fuzzyNumbers: [[0,0.16,0.33], [0.33,0.49,0.66], [0.66,0.83,1]] + }; + + // Verificamos a qué label nuestro valor es más perteneciente. + let membershipFunction = function(n, fn) { + let distL = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[0])) * 100) / 100;; + let distM = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[1])) * 100) / 100;; + let distH = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[2])) * 100) / 100;; + + let memberValue = Math.min(distL,distM,distH); + let memberLabel = ''; + + switch(memberValue) { + case distL: + memberLabel = fn.linguisticLabels[0]; + break; + case distM: + memberLabel = fn.linguisticLabels[1]; + break; + case distH: + memberLabel = fn.linguisticLabels[2]; + break; + default: + memberLabel = fn.linguisticLabels[0]; + } + + return memberLabel; + }; + + let resultLabel = membershipFunction(response.puntaje, fuzzySet); + + + + // Chequeamos si no pasamos el criterio para negativizar argumentos... + if (resultLabel === 'low' || resultLabel === 'medium') { criterias[0].argument = 'en general, el texto abusa de la opinión y el lenguaje subjetivo'; criterias[1].argument = 'usa una terminologia complejos son solo pueden ser entendidos por expertos'; criterias[2].argument = 'el contenido de este texto es dificil de chequear'; criterias[3].argument = 'observé que el autor suele asumir hechos y expresar su posición sin justificarse'; } + // Computamos tiempo de lectura que usaremos en la rta del agente.. + const tiempoLectura = Math.round(doc.readingTime()); - // Computamos tiempo de lectura que usaremos en la rta del agente.. - const tiempoLectura = Math.round(doc.readingTime()); - - // Si pasa umbral.. - if (sim > 0.65) { + // Si pasa umbral.. + if (resultLabel === 'high') { // Vamos a rankear cada argumento de mayor a menor... criterias.sort((a, b) => parseFloat(b.rating) - parseFloat(a.rating)); response.texto = `Analicé el articulo de ${response.nombre} sobre ${response.tema[0]} y ${response.tema[1]}. Te recomiendo leerlo ya que ` + `(A) ${criterias[0].argument} , (B) ${criterias[1].argument} y (C) ${criterias[2].argument}. Espero que te interese, te va a llevar unos ${tiempoLectura} minutos leerlo. Gracias por confiar en mi. `; From 0228643602c2fcefa303f8c7dbf9e86ebfecfff4 Mon Sep 17 00:00:00 2001 From: Patelo Date: Sun, 25 Nov 2018 22:25:39 +0000 Subject: [PATCH 07/10] Update index.js --- routes/index.js | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/routes/index.js b/routes/index.js index 5afd742..ff5d0d0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -318,17 +318,17 @@ router.post('/filter', (req, res, next) => { // body = striptags(body, [], ' '); body = $('body').text(); - // SACAMOS LA INFORMACION QUE PODEMOS DE LOS METATAGS DE OPENGRAPH + // SACAMOS LA INFORMACION QUE PODEMOS DE LOS METATAGS DE OPENGRAPH response.imagethum = $('meta[property="og:image"]').attr('content'); response.titulo = $('meta[property="og:title"]').attr('content'); response.descripcion = $('meta[property="og:description"]').attr('content'); - // USAMOS LORCA PARA ANALIZAR EL TEXTO LINGUISTICAMENTE - const doc = lorca(body); + // USAMOS LORCA PARA ANALIZAR EL TEXTO LINGUISTICAMENTE + const doc = lorca(body); - // Para detectar el posible tema del texto , agarramos todo el texto y le sacamos las stopwords... - let nText = String(body).toLowerCase(); + // Para detectar el posible tema del texto , agarramos todo el texto y le sacamos las stopwords... + let nText = String(body).toLowerCase(); const stopWords = [response.nombre, ' al ', ' no ', ' si ', ' su ', 'qué', 'más', ' uno ', ' como ', ' con ', 'La ', 'El ', 'Lo ', ' son ', 'Los ', 'No ', ' las ', ' sus ', 'Su ', ' con ', 'Te ', 'Para ', ' yo ', ' el ', ' se ', ' por ', ' vos ', ' un ', ' de ', ' tu ', ' para ', ' el ', ' lo ', ' los ', ' ella ', ' de ', ' es ', ' una ', ' fue ', ' tiene ', ' la ', ' y ', ' del ', ' los ', ' que ', ' a ', ' en ', ' el ']; @@ -339,7 +339,7 @@ router.post('/filter', (req, res, next) => { // Usamos lorca para manipular la linguistica del texto nText = lorca(String(nText)); - // El metodo concordance nos dice cual es las palabras más repetidas... sacamos las dos primeras que se repitan que no sean stopwords. + // El metodo concordance nos dice cual es las palabras más repetidas... sacamos las dos primeras que se repitan que no sean stopwords. nText = nText.concordance().sort(2).get(); response.tema = Object.keys(nText).map(key => [String(key), nText[key]]); @@ -355,26 +355,26 @@ router.post('/filter', (req, res, next) => { // AHORA vamos a computar los atributos de nuestra decisión en base a lo que encontremos en el texto... let objectivity = 0; let argumentativity = 0; - let verificability = 0; - const accesibility = 0; - let assertiveness = 0; - let powerConcentration = 0; + let verificability = 0; + const accesibility = 0; + let assertiveness = 0; + let powerConcentration = 0; // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. for (var i = 0; i != doc.sentences().get().length; i++) { - if (verificabilidad.classify(doc.sentences().get()[i]) === 'verificable') { - verificability += 1; + if (verificabilidad.classify(doc.sentences().get()[i]) === 'verificable') { + verificability += 1; } - if (objetividad.classify(doc.sentences().get()[i]) === 'objetivo') { - objectivity += 1; + if (objetividad.classify(doc.sentences().get()[i]) === 'objetivo') { + objectivity += 1; } - if (argumentatividad.classify(doc.sentences().get()[i]) === 'argumentado') { - argumentativity += 1; + if (argumentatividad.classify(doc.sentences().get()[i]) === 'argumentado') { + argumentativity += 1; } - if (asertividad.classify(doc.sentences().get()[i]) === 'asertivo') { - assertiveness += 1; + if (asertividad.classify(doc.sentences().get()[i]) === 'asertivo') { + assertiveness += 1; } // USAMOS ESTA HEURISTICA PARA VER SI EL TEXTO CONTIENE FUENTES. @@ -385,11 +385,11 @@ router.post('/filter', (req, res, next) => { } - // Computamos objetividad... - // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. - response.objetividad = Math.round(objectivity / doc.sentences().get().length) * 10; + // Computamos objetividad... + // Cantidad de oraciones objetivas / Total de oraciones del texto , en un indice del 1 al 10. + response.objetividad = Math.round(objectivity / doc.sentences().get().length) * 10; - // Computamos accesibilidad... + // Computamos accesibilidad... // Facilidad de lectura (LorcaJS) en un indice del 1 al 10 const readability = Math.round((doc.ifsz().get() / 100)) * 10; @@ -407,7 +407,7 @@ router.post('/filter', (req, res, next) => { if (response.fuentes.length > 5) { sources = 10; } else { - sources = (response.fuentes.length / 5) * 10; + sources = (response.fuentes.length / 5) * 10; } response.verificabilidad = Math.round((verificability * 0.8 + sources * 0.2) / 2); From a3c3d0d374956df913ad9a11372e2e8e597c53b5 Mon Sep 17 00:00:00 2001 From: Patelo Date: Fri, 28 Dec 2018 14:37:02 +0000 Subject: [PATCH 08/10] Update readme.md --- readme.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 5e5f789..c48a6ee 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,10 @@ # Asistente Inteligente analizador de noticias BETA +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/917d07fdcd1d4804b3b846995902f3dc)](https://app.codacy.com/app/patelotech/NewsDSS-Argentina?utm_source=github.com&utm_medium=referral&utm_content=patelotech/NewsDSS-Argentina&utm_campaign=Badge_Grade_Dashboard) +[![build](https://travis-ci.org/patelotech/NewsDSS-Argentina.svg?branch=master)](https://travis-ci.org/patelotech/NewsDSS-Argentina) +[![dependencies Status](https://david-dm.org/patelotech/NewsDSS-Argentina/status.svg)](https://david-dm.org/patelotech/NewsDSS-Argentina) +[![Known Vulnerabilities](https://snyk.io/test/github/patelotech/topsis/badge.svg?targetFile=package.json)](https://snyk.io/test/github/patelotech/topsis?targetFile=package.json) + ## Descripción Agente inteligente que analiza el texto de un artículo noticiario Argentino para analizar si es confiable de leer o no y en base a dicho resultado, realizar una recomendación accionable argumentada. @@ -146,8 +151,8 @@ El agente formula en su respuesta sintetizada un orden de prioridad de los 3 arg ### Correción y linteo: -* Chequear errores: `npm run check` -* Chequear y corregir errores: `npm run lint` or `npm run check -- --fix` +* Chequear errores: `npm run lint` +* Chequear y corregir errores: `npm run lint-fix` or `npm run lint -- --fix` ## Endpoints @@ -216,5 +221,7 @@ response = { * Freire, S. M., Nascimento, A., & de Almeida, R. T. (2018). A Multiple Criteria Decision Making System for Setting Priorities. World Congress on Medical Physics and Biomedical Engineering 2018, 357–361. doi:10.1007/978-981-10-9035-6_65 * Mariana Arburua (2017). Método de Decisión Multicriterio: TOPSIS. Youtube. Retrieved from https://www.youtube.com/watch?v=p8WyEn14Cto + ### Razonamiento rebatible en problemas de decisión multi-criterio * Ferretti, E., Errecalde, M., García, A. J., & Simari, G. R. (2007, May). An application of defeasible logic programming to decision making in a robotic environment. In International Conference on Logic Programming and Nonmonotonic Reasoning (pp. 297-302). Springer, Berlin, Heidelberg. + From e8683aa06e2cd22c94e5b35db4fe97ac331ef13a Mon Sep 17 00:00:00 2001 From: Patelo Date: Fri, 28 Dec 2018 14:38:14 +0000 Subject: [PATCH 09/10] Update index.js --- routes/index.js | 76 ++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/routes/index.js b/routes/index.js index ff5d0d0..f0dde48 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,5 +1,9 @@ +/** *************************************************************************************************************** +****************************************************************************************************************** + /***************************************************************************************************************** ****************************************************************************************************************** + index - index.js ****************************************************************************************************************** ****************************************************************************************************************** @@ -45,9 +49,11 @@ Vamos a partir de una URL de una noticia. En caso que el texto no pase el umbral el agente recomienda otros sitios que hablen del mismo tema. - +<<<<<<< HEAD + ****************************************************************************************************************** -*****************************************************************************************************************/ +**************************************************************************************************************** */ + const express = require('express'); @@ -72,10 +78,16 @@ const natural = require('natural'); const lorca = require('lorca-nlp'); + +/** *************************************************************************************************************** + Entrenamiento de clasificadores +***************************************************************************************************************** */ + /***************************************************************************************************************** Entrenamiento de clasificadores ******************************************************************************************************************/ + // Objetividad const objetividad = new natural.BayesClassifier(); @@ -361,7 +373,9 @@ router.post('/filter', (req, res, next) => { let powerConcentration = 0; - // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. + + // Pasa por cada oración y la hace clasificador por el clasificador de Bayes que entrenamos con LorcaJS. + for (var i = 0; i != doc.sentences().get().length; i++) { if (verificabilidad.classify(doc.sentences().get()[i]) === 'verificable') { verificability += 1; @@ -407,7 +421,11 @@ router.post('/filter', (req, res, next) => { if (response.fuentes.length > 5) { sources = 10; } else { + + sources = (response.fuentes.length / 5) * 10; + sources = (response.fuentes.length / 5) * 10; + } response.verificabilidad = Math.round((verificability * 0.8 + sources * 0.2) / 2); @@ -496,43 +514,43 @@ router.post('/filter', (req, res, next) => { const score = criterias[0].rating + criterias[1].rating + criterias[2].rating + criterias[3].rating; response.puntaje = Math.round(sim * 100) / 100; - + + // Creamos un fuzzy set para definir nuestro umbral de decisión let fuzzySet = { linguisticLabels: ['low', 'medium', 'high'], - fuzzyNumbers: [[0,0.16,0.33], [0.33,0.49,0.66], [0.66,0.83,1]] + fuzzyNumbers: [[0, 0.16, 0.33], [0.33, 0.49, 0.66], [0.66, 0.83, 1]], }; - // Verificamos a qué label nuestro valor es más perteneciente. - let membershipFunction = function(n, fn) { - let distL = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[0])) * 100) / 100;; - let distM = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[1])) * 100) / 100;; - let distH = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[2])) * 100) / 100;; - - let memberValue = Math.min(distL,distM,distH); + // Verificamos a qué label nuestro valor es más perteneciente. + const membershipFunction = function (n, fn) { + const distL = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[0])) * 100) / 100; + const distM = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[1])) * 100) / 100; + const distH = Math.round(Math.abs(n - math.median(fn.fuzzyNumbers[2])) * 100) / 100; + + const memberValue = Math.min(distL, distM, distH); let memberLabel = ''; - - switch(memberValue) { - case distL: - memberLabel = fn.linguisticLabels[0]; - break; - case distM: - memberLabel = fn.linguisticLabels[1]; - break; + + switch (memberValue) { + case distL: + memberLabel = fn.linguisticLabels[0]; + break; + case distM: + memberLabel = fn.linguisticLabels[1]; + break; case distH: memberLabel = fn.linguisticLabels[2]; break; - default: - memberLabel = fn.linguisticLabels[0]; - } - + default: + memberLabel = fn.linguisticLabels[0]; + } + return memberLabel; - }; - - let resultLabel = membershipFunction(response.puntaje, fuzzySet); - - + }; + + const resultLabel = membershipFunction(response.puntaje, fuzzySet); + // Chequeamos si no pasamos el criterio para negativizar argumentos... if (resultLabel === 'low' || resultLabel === 'medium') { criterias[0].argument = 'en general, el texto abusa de la opinión y el lenguaje subjetivo'; From 95f0e4886d663f6aa30f534c3edbe6b10b61bdc0 Mon Sep 17 00:00:00 2001 From: Patelo Date: Fri, 28 Dec 2018 14:39:29 +0000 Subject: [PATCH 10/10] Delete readme.md --- readme.md | 227 ------------------------------------------------------ 1 file changed, 227 deletions(-) delete mode 100644 readme.md diff --git a/readme.md b/readme.md deleted file mode 100644 index c48a6ee..0000000 --- a/readme.md +++ /dev/null @@ -1,227 +0,0 @@ -# Asistente Inteligente analizador de noticias BETA - -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/917d07fdcd1d4804b3b846995902f3dc)](https://app.codacy.com/app/patelotech/NewsDSS-Argentina?utm_source=github.com&utm_medium=referral&utm_content=patelotech/NewsDSS-Argentina&utm_campaign=Badge_Grade_Dashboard) -[![build](https://travis-ci.org/patelotech/NewsDSS-Argentina.svg?branch=master)](https://travis-ci.org/patelotech/NewsDSS-Argentina) -[![dependencies Status](https://david-dm.org/patelotech/NewsDSS-Argentina/status.svg)](https://david-dm.org/patelotech/NewsDSS-Argentina) -[![Known Vulnerabilities](https://snyk.io/test/github/patelotech/topsis/badge.svg?targetFile=package.json)](https://snyk.io/test/github/patelotech/topsis?targetFile=package.json) - -## Descripción -Agente inteligente que analiza el texto de un artículo noticiario Argentino para analizar si es confiable de leer o no y en base a dicho resultado, realizar una recomendación accionable argumentada. - -Analisis inspirado en los 5 filtros de Chomsky en su libro 'Consenso Fabricado'. - -El propósito de esta aplicación que darle a los usuarios una herramienta para elaborar juicios informados acerca de la confiabilidad de una noticia. -Tendremos en cuenta una gran diversidad de variables tales cómo, ¿Es una noticia que pertenece a un medio de un grupo concetrado de poder? ¿Quién financia ese medio? ¿Cómo monetiza? ¿En el texto predomina el lenguaje subjetivo u objetivo? ¿Qué tan actual es la noticia? ¿Usan varias fuentas para afirmar lo qué dicen? ¿Las afirmaciones del texto estan argumentadas o no? - -![Interfaz](readme.jpg) - -## Autor - -* Patricio J. Gerpe - - -## Tech stack - -### Back-End -* NodeJS: v8.11.1 -* Express: 4.16.0 - -#### Dependencies: - -##### Web scraping -* "cheerio": "^1.0.0-rc.2" - -##### Procesamiento de lenguaje natural -* "lorca-nlp": "^1.0.12" -* "natural": "^0.6.2" - -##### Matematica -* "euclidean-distance": "^1.0.0", -* "compute-hamming": "^1.1.0", -* "mathjs": "^5.2.3" - - -### DataSets -* FOPEA Mapa de medios - http://mapademediosfopea.com/analisis/ -* Corpus de entrenamiento con ejemplos de oraciones subjetivas y objetivas extraidas de noticias reales de Argentina ya labeleadas. -* Corpus de entrenamiento con ejemplos de oraciones que expresan argumentos y oraciones que asumen un hecho sin justificación ya labeleadas. -* Corpus de entrenamiento con ejemplos de afirmaciones que pueden ser chequeables y afirmaciones que no pueden ser chequeables ya labeleadas. -* Corpus de entrenamiento con ejemplos de oraciones con lenguaje asertivo, agresivo y pasivo ya labeleadas. -! ATENCION --> Los corpuses son demasiado pequeños por ahora. - -### Front-End -* Material Dashboard - https://github.com/creativetimofficial/material-dashboard -* Bootstrap 4 -* Templating language: EJS - -#### Avatar 3D + Text to Speech -* Bot Libre - https://www.botlibre.com - - -## Set-Up - -1. `npm i` -2. `npm start` - - -## ¿Cómo funciona? - - -Nuestro agente inteligente tiene como objetivo recomendar que articulos de noticias que son confiables para leer y cuáles no lo son. - -Tomaremos como atributos de nuestra decisión ciertos atributos deseables en el texto de la noticia: - -1. A1: Objetividad -Entenderemos a este atributo como la cantidad de oraciones objetivas presentes en el texto sobre el total de oraciones del texto. - -2. A2: Accesibilidad -Accesibilidad posee los siguientes sub-atributos: - 1. A2a: Indice de lectura. Se usa un indicador built-in de LorcaJS que indica que tan sencillo es el articulo para leer. - 2. A2b: Presencia de oraciones con lenguaje asertivo (clasificador de Bayes) sobre total de oraciones del texto. - -3. A3: Verificabilidad - 1. A3a: Chequeabilidad. Se usa chequeabot de Chequeado. (Ahora se esta usando un clasificador de Bayes con el corpus de entrenamiento). - 2. A3b: Cantidad de fuentes. Se usa una heuristica para detectar si el texto tiene fuentes o no. - -4. A4: Confiabilidad - 1. A4a: Presencia de oraciones argumentadas (Clasificador de Bayes) sobre total de oraciones. - 2. A4b: Fecha de publicación del articulo > 2015 (Verificamos si el texto es actual) - 3. A4c: Independencia (Verificamos si el medio es independiente o depende de un grupo economico o del estado.) - - -Cada atributo recibe un peso (weight) que representa su importancia. --> Se puede usar el metodo HPA para hacer este proceso. -Por ahora se hizo de manera manual. - -1. A1: Objetividad / Peso: 40% - -2. A2: Accesibilidad / Peso: 10% - 1. A2a: Indice de lectura. / Peso: 90% - 2. A2b: Presencia de oraciones con lenguaje asertivo. / Peso: 10% - -3. A3: Verificabilidad / Peso: 30% - 1. A3a: Chequeabilidad. / Peso: 80% - 2. A3b: Cantidad de fuentes. / Peso: 20% - -4. A4: Confiabilidad / Peso: 20% - 1. A4a: Presencia de oraciones argumentadas / Peso: 5% - 2. A4b: Fecha de publicación. Por ahora no se tiene en cuenta. En futuras versiones si... - 3. A4c: Independencia / Peso: 95% - - -Entonces... - -El usuario nos envia una URL. - -Nosotros hacemos una **request** a esa URL y usamos **Cheerio** para manipular su contenido. -Extraemos información de sus metatags de opengraph tales como el título, descripción e imagen de thumbnail. -Tenemos una base de datos de FOPEA con un listado de medios y sus respectivos dueños. A partir del nombre de medio lo buscamos en la lista del csv -(primero preprocesandolo con **csvtjson**). Allí encontraremos información de que grupo economico es dueño de dicho medio y en qué porcentaje. -Ahora analizamos el texto con la librería de NLP **LorcaJS**. Tokenizamos el texto en oraciones. Y pasamos cada oración por nuestros clasificadores de Bayes. - -Computamos cada uno de nuestros atributos en base de lo observado en el texto. - -Se rankea cada atributo en relación a su valor y su peso, y luego se computa un indice de decisión para el texto. - -**Algoritmo** TOPSIS: - -1. Se calcula la distancia ecluidea entre el puntaje obtenido y el peor puntaje posible. -![](https://wikimedia.org/api/rest_v1/media/math/render/svg/da482a8c2f0902dad096aab66a0459fecc20a23d) - -2. Se calcula la distancia ecluidea entr el puntaje obtenido y el mejor puntaje posible. -![](https://wikimedia.org/api/rest_v1/media/math/render/svg/693633e4b0170769f93ed89c9714256698368b7a) - -3. Se calcula el indice de performance. -![](https://wikimedia.org/api/rest_v1/media/math/render/svg/cd78e6f8cdbeea32bd6c3955533c09287a69dd41) - -Si el indice de performance supera un umbral predeterminado, se recomienda leer el archivo. En caso contrario el agente recomienda buscar la noticia desde otras fuentes. - -El agente formula en su respuesta sintetizada un orden de prioridad de los 3 argumentos más relevantes para tomar esa decisión en base al ranking realizado de atributos. - - -## Debugging y linteo: - -### Estilo de código: - -* AIRBNB -[AIRBNB JS CODE STYLE](https://dev.mysql.com/doc/ "AIRBNB JS CODE STYLE") - -### Configuración - -* Eslint v-4.19.1 // AIRBNB Configuration - -### Correción y linteo: - -* Chequear errores: `npm run lint` -* Chequear y corregir errores: `npm run lint-fix` or `npm run lint -- --fix` - - -## Endpoints - -Route: **/filter** - -```javascript -PostBody = { - url: -}; -``` - -```javascript -response = { - url: '', - imagethum: '', - titulo: '', - media: '', - mediaIndex: 0, - nombre: '', - dueno: '', - financiamiento: '', - monetizacion: '', - esGrupoConcentrado: '', - tema: [], - temas_relacionados: [], - fuentes: [], - porcentaje: '', - objetividad: 0, - verificabilidad: 0, - accesibilidad: 0, - confiabilidad: 0, - puntaje: 0, - texto: '', - descripcion: '' } -``` - -## Limitaciones - -* Los corpus de entrenamiento son demasiado pequeños, lo cuál hace que este proyecto no este funcional de ninguna manera. Este proyecto necesita voluntarios para incrementar, emplolijar y labelear corpus de noticias. -* Los pesos asignados y sus valores son asignados manualmente, a juicio subjetivo. -* No existe aun un dataset con información financiera de los concentrados economicos de medios. -* No necesariamente un clasificador de Bayes es el método más efectivo para clasificar corpus. -* Hay que solicitar permiso para usar el chequeabot de chequeado. -* Avatares animados de BotLibre son limitados, en este caso se eligió uno acorde al theme de diseño, aunque podría haber sido uno sin genero especifico. -* Es una versión BETA no libre de bugs. -* La voz se sintetiza con un motor gratuito de voz libre, esta pensado para el italiano. Para un TTS en español hay que integrar con una solución de cloud que tenga soporte en ese idioma. - -## Referencias y bibliografía de soporte: - -* Edward S.. Herman, & Chomsky, N. (1988). Manufacturing consent: The political economy of the mass media. London: Vintage. -* Al Jazeera English (2017). Noam Chomsky - The 5 Filters of the Mass Media Machine. Youtube. Retrieved from https://www.youtube.com/watch?v=34LGPIXvU5M - -### NPM packages documentation -* LorcaJS: https://github.com/dmarman/lorca - -### AHP (Algorithm: Analytic Hierarchy proccess) -* Saaty, T. L. (1986). Axiomatic Foundation of the Analytic Hierarchy Process. Management Science, 32(7), 841. doi:10.1287/mnsc.32.7.841 -* Saaty, R. W. (1987). The analytic hierarchy process—what it is and how it is used. Mathematical Modelling, 9(3-5), 167. doi:10.1016/0270-0255(87)90473-8 -* Manoj Mathew (2018). Analytic Hierarchy Process (AHP). Youtube. Retrieved from https://www.youtube.com/watch?v=J4T70o8gjlk - -### TOPSIS (Algorithm: Technique for Order of Preference by Similarity to Ideal Solution) -* Hwang, C.L.; Yoon, K. (1981). Multiple Attribute Decision Making: Methods and Applications. New York: Springer-Verlag. -* Hwang, C.L.; Lai, Y.J.; Liu, T.Y. (1993). "A new approach for multiple objective decision making". Computers and Operational Research. 20: 889–899 -* Değer Alper, Canan Başdar (2017). A Comparison of TOPSIS and ELECTRE Methods: An Application on the Factoring Industry. -* Freire, S. M., Nascimento, A., & de Almeida, R. T. (2018). A Multiple Criteria Decision Making System for Setting Priorities. World Congress on Medical Physics and Biomedical Engineering 2018, 357–361. doi:10.1007/978-981-10-9035-6_65 -* Mariana Arburua (2017). Método de Decisión Multicriterio: TOPSIS. Youtube. Retrieved from https://www.youtube.com/watch?v=p8WyEn14Cto - - -### Razonamiento rebatible en problemas de decisión multi-criterio -* Ferretti, E., Errecalde, M., García, A. J., & Simari, G. R. (2007, May). An application of defeasible logic programming to decision making in a robotic environment. In International Conference on Logic Programming and Nonmonotonic Reasoning (pp. 297-302). Springer, Berlin, Heidelberg. -