#
#   python_similitud_frases_spacy_sinonimos.py
#
#   Lee un archivo CSV1 con frases en español, lee otro archivo CSV2 con frases sinónimas al anterior
#       y verifica su similitud usando la biblioteca "spacy"
#
#   REQUISITOS:
#       Descargar el modelo en Español con:
#           $ python3 -m spacy download es_core_news_md     # Modelo mediano
#           $ python3 -m spacy download es_core_news_lg     # Modelo grande (recomendado)
#
#   Rogelio Ferreira Escutia - noviembre/2026
#

# Bibliotecas necesarias (no requieren instalar nada a través de PyPi)
import csv
import sys
import time
import datetime

# Bibliotecas utilizadas que hay que instalar
import ollama
from ollama import Client, ResponseError
import numpy as np
import spacy

# Constantes
CSV1 = "dataset_034_robot_ordenes_spanish_100.csv"                  # CSV1 - Lista de órdenes en español
CSV2 = "dataset_035_robot_ordenes_spanish_sinonimos_100"            # CSV2 - Lista de sinónimos del anterior

# Modelos utilizados
#   Modelos por tamaño de parámetros    Porcentaje  Tiempo de prueba con 3 frases en MacBook Pro M3 18GB RAM
LLM_MODEL = "embeddinggemma-2:270m"     # 92.13%    0:00:02.491769      RECOMENDADO
LLM_MODEL = "embeddinggemma:300m"       # 84.68%    0:00:01.335469
LLM_MODEL = "bge-m3:567m"               # 91.33%    0:00:01.837850
LLM_MODEL = "qwen3-embedding:0.6b"      # 93.23%    0:00:05.898018

# Función que pregunta por los metadatos
def preguntar_metadatos():
    global LLM_MODEL
    global device
    global llm_model_name
    global test_name
    device = ""
    llm_model_name = ""
    test_name = ""
    lista = []
    lista.append("Metadatos - Rogelio Ferreira Escutia")
    print("\nPreguntar Metadatos:")
    while True:
        print("\nSeleccionar tipo de pruebas:")
        print("\t1.- Generar un dataset de Sinónimos en Español a partir de otro dataset")
        print("\t2.- Generar un dataset de Antónimos en Español a partir de otro dataset")
        print("\t3.- Calcula la similitud entre frases y sus sinónimos usando la biblioteca 'spacy'")
        print("\t4.- Calcula la similitud entre frases y sus sinónimos con ayuda de un LLM")
        print("\t5.- Calcula la similitud entre frases y sus antónimos con ayuda de un LLM")
        print("\t6.- Calcula la similitud entre frases de 2 conjuntos de órdenes totalmente diferente con ayuda de un LLM")
        opcion = input("Elige opción (1-6): ")
        if opcion == "1":
            lista.append("Prueba: Generar un dataset de Sinónimos en Español a partir de otro dataset")
            test_name = "sinonimos"
            break
        if opcion == "2":
            lista.append("Prueba: Generar un dataset de Antónimos en Español a partir de otro dataset")
            test_name = "antonimos"
            break
        if opcion == "3":
            lista.append("Prueba: Calcula la similitud entre frases y sus sinónimos usando la biblioteca 'spacy'")
            test_name = "spacy_sinonimos"
            break
        if opcion == "4":
            lista.append("Prueba: Calcula la similitud entre frases y sus sinónimos con ayuda de un LLM")
            test_name = "similitud"
            break
        if opcion == "5":
            lista.append("Prueba: Calcula la similitud entre frases y sus antónimos con ayuda de un LLM")
            test_name = "antonimos"
            break
        if opcion == "6":
            lista.append("Prueba: Calcula la similitud entre frases de 2 conjuntos de órdenes totalmente diferente con ayuda de un LLM")
            test_name = "diferentes"
            break
        else:
            print("\nOpción no válida. Por favor, introduce 1 a 3")
    while True:
        print("\nSeleccionar Dispositivo:")
        print("\t1.- Jetson Nano con 4 GB RAM")
        print("\t2.- Raspberry Pi 5 con 8 GB RAM")
        print("\t3.- Orin Nano con 8 GB RAM")
        print("\t4.- Raspberry Pi 5 con 16 GB RAM")
        print("\t5.- MiniPC Linux con 16 GB RAM")
        print("\t6.- MacBook Pro M3 con 18 GB RAM")
        opcion = input("Elige opción (1-6): ")
        if opcion == "1":
            lista.append("Dispositivo: Jetson Nano con 4 GB RAM")
            device = "jetson_nano"
            break
        if opcion == "2":
            lista.append("Dispositivo: Raspberry Pi 5 con 8 GB RAM")
            device = "raspberry_pi_5"
            break
        if opcion == "3":
            lista.append("Dispositivo: Orin Nano con 8 GB RAM")
            device = "orin_nano"
            break
        if opcion == "4":
            lista.append("Dispositivo: Raspberry Pi 5 con 16 GB RAM")
            device = "raspberry_pi_5"
            break
        if opcion == "5":
            lista.append("Dispositivo: MiniPC Linux con 16 GB RAM")
            device = "mini_pc_linux"
            break
        if opcion == "6":
            lista.append("Dispositivo: MacBook Pro M3 con 18 GB RAM")
            device = "macbook_pro_m3"
            break
        else:
            print("\nOpción no válida. Por favor, introduce 1 a 6")
    while True:
        print("\nSeleccionar Algoritmo Utilizado:")
        print("\t1.- No se utilizó ningún Algoritmo específico")
        print("\t2.- Similitud Coseno")
        print("\t3.- Biblioteca 'spacy'")
        opcion = input("Elige opción (1-2): ")
        if opcion == "1":
            lista.append("Algoritmo: No se utilizó ningún Algoritmo específico")
            break
        if opcion == "2":
            lista.append("Algoritmo: Similitud Coseno")
            break
        if opcion == "3":
            lista.append("Algoritmo: Biblioteca 'spacy'")
            break
        else:
            print("\nOpción no válida. Por favor, introduce 1 a 2")
    while True:
        print("\nSeleccionar Modelo Utilizado (LLM):")
        print("\t1.- No se utilizó ningún Modelo")
        print("\t2.- embeddinggemma-2:270m")
        print("\t3.- embeddinggemma:300m")
        print("\t4.- bge-m3:567m")
        print("\t5.- qwen3-embedding:0.6b")
        print("\t6.- gemma4:12b")
        print("\t7.- qwen2.5:14b")
        print("\t8.- translategemma:12b")
        opcion = input("Elige opción (1-8): ")
        if opcion == "1":
            lista.append("LLM: No se utilizó ningún Modelo")
            LLM_MODEL = "none"
            llm_model_name = "none"
            break
        if opcion == "2":
            lista.append("LLM: embeddinggemma-2:270m")
            LLM_MODEL = "embeddinggemma-2:270m"
            llm_model_name = "embeddinggemma-2_270m"
            break
        if opcion == "3":
            lista.append("LLM: embeddinggemma:300m")
            LLM_MODEL = "embeddinggemma:300m"
            llm_model_name = "embeddinggemma_300m"
            break
        if opcion == "4":
            lista.append("LLM: bge-m3:567m")
            LLM_MODEL = "bge-m3:567m"
            llm_model_name = "bge-m3_567m"
            break
        if opcion == "5":
            lista.append("LLM: qwen3-embedding:0.6b")
            LLM_MODEL = "qwen3-embedding:0.6b"
            llm_model_name = "qwen3-embedding_0-6b"
            break
        if opcion == "6":
            lista.append("LLM: gemma4:12b")
            LLM_MODEL = "gemma4:12b"
            llm_model_name = "gemma4_12b"
            break
        if opcion == "7":
            lista.append("LLM: qwen2.5:14b")
            LLM_MODEL = "qwen2.5:14b"
            llm_model_name = "qwen2-5_14b"
            break
        if opcion == "8":
            lista.append("LLM: translategemma:12b")
            LLM_MODEL = "translategemma:12b"
            llm_model_name = "translategemma_12b"
            break
        else:
            print("\nOpción no válida. Por favor, introduce 1 a 6")
    return lista

# Recibe una lista y con ella genera un archivo de texto
def generar_archivo_txt(lista, nombre_archivo):
    with open(nombre_archivo, "w", encoding="utf-8") as f:
       for i, elemento in enumerate(lista):
          f.write(str(elemento))
          if i < len(lista) - 1:
             f.write("\n")
    return

# Recibe el nombre de un archivo CSV, lo lee y lo pasa a una lista
def cargar_csv(nombre_archivo: str):
    filas = []
    with open(nombre_archivo, mode='r', encoding='utf-8', newline='') as archivo:
        lector = csv.reader(archivo)
        for fila in lector:
            filas.append(fila)
    return filas

# Calcular en porcentaje la similud de 2 frases
def calcular_similaridad(frase_1: str, frase_2: str, LLM_MODEL: str):
    # Convertir las frases a minúsculas
    frase_1 = frase_1.lower()
    frase_2 = frase_2.lower()
    # Generando los Words Embeddings
    word_embedding_1 = ollama.embed(model=LLM_MODEL, input=frase_1)
    word_embedding_2 = ollama.embed(model=LLM_MODEL, input=frase_2)
    # Creando los vectores
    vector_1 = np.array(word_embedding_1["embeddings"][0])
    vector_2 = np.array(word_embedding_2["embeddings"][0])
    # Cálculo de la Similitud Coseno: (A · B) / (||A|| * ||B||)
    producto_punto = np.dot(vector_1, vector_2)
    normal_vector_1 = np.linalg.norm(vector_1)
    normal_vector_2 = np.linalg.norm(vector_2)
    if normal_vector_1 == 0 or normal_vector_2 == 0:
        return 0.0
    similitud = producto_punto / (normal_vector_1 * normal_vector_2)
    # Convertir el resultado en una escala de porcentaje (0.0% a 100.0%)
    porcentaje_similitud = max(0.0, float(similitud)) * 100
    return round(porcentaje_similitud, 2)

# Calcular en porcentaje la similud de 2 frases usando "spacy"
def calcular_similitud_spacy(frase_1: str, frase_2: str):
    global nlp
    # Convertir las frases a minúsculas
    frase_1 = frase_1.lower()
    frase_2 = frase_2.lower()
    texto_1 = nlp(frase_1)
    texto_2 = nlp(frase_2)
    porcentaje_similitud = texto_1.similarity(texto_2) * 100
    return round(porcentaje_similitud, 2)

#
#       PROGRAMA PRINCIPAL
#
print("\n*** Verificar Similitud usando 'spacy' ***\n")

# Inicializar los Metadatos
metadatos = []
device = ""
llm_model_name = ""
test_name = ""

# Si se usa la biblioteca 'spacy'
#   Cargar el modelo en Español con vectores de palabras (Large)
nlp = spacy.load("es_core_news_lg")

# Preguntar Metadatos
metadatos = preguntar_metadatos()

# Cargando el CSV1
print("\n\tCargando CSV1...")
lista_csv1 = cargar_csv(CSV1)

# Cargando el CSV2
print("\n\tCargando CSV2...")
lista_csv2 = cargar_csv(CSV2)

# Registra el inicio del procesamiento
print("\nMODELO: ", LLM_MODEL)
lista_similitud = []
contador = 0
print("\n\tINICIANDO Procesamiento...\n")
inicio = datetime.datetime.now()
for indice1, frase1 in enumerate(lista_csv1):
    similitud_maxima = float('-inf')
    similitud_margen = float('-inf')
    segundo_maximo = float('-inf')
    acierto = False
    for indice2, frase2 in enumerate(lista_csv2):
        #similitud = calcular_similaridad(str(frase1), str(frase2), LLM_MODEL)                  # Para cuando se usa un LLM
        similitud = calcular_similitud_spacy(str(frase1), str(frase2))
        contador = contador + 1
        print(contador)                                                                         # DEPURACION (se puede comentar)
        #print(similitud, "% -> ", indice1, ": ", frase1, " contra ", indice2, ": ", frase2)    # DEPURACION (se puede comentar)
        if similitud > similitud_maxima:
            segundo_maximo = similitud_maxima
            similitud_maxima = similitud
            indice_temporal = indice2
        elif similitud > segundo_maximo:
            segundo_maximo = similitud
    # Calcular la distancia si ya tenemos al menos dos elementos
    if segundo_maximo != float('-inf'):
        similitud_margen = round((similitud_maxima - segundo_maximo), 2)
        #print("Margen: ", similitud_margen)                                                    # DEPURACION (se puede comentar)
    else:
        similitud_margen = "N/A (se necesita más de un registro)"
    if indice1 == indice_temporal:
        acierto = True
        lista_similitud.append([similitud_maxima,acierto,similitud_margen])
    else:
        lista_similitud.append([similitud_maxima,acierto,similitud_margen])
    print("\n")
    #print("La Máxima similitud es: ", similitud_maxima, " - Indice: ", indice_temporal, "\n")  # DEPURACION (se puede comentar)
fin = datetime.datetime.now()
tiempo_total = fin - inicio
print("\n\tTERMINANDO Procesamiento...\n")
print("Tiempo total: " + str(tiempo_total))
print("\nLista de similitudes:\n", lista_similitud)

# Extraer solo los números para el promedio
numeros = [fila[0] for fila in lista_similitud]
total_registros = len(numeros)
promedio_similitudes = round((sum(numeros) / total_registros), 2)

# Contar los True (se suman directamente)
total_trues = sum(fila[1] for fila in lista_similitud)

# Impresión de Resultados
print("\nTotal de registros analizados: ", total_registros)
print("Promedio de Similitudes: ", promedio_similitudes, "%")
print("Cantidad de Aciertos: ", total_trues)

# Extraer solo los números para el promedio
numeros = [fila[2] for fila in lista_similitud]
promedio_margen = round((sum(numeros) / len(numeros)), 2)
print("Promedio del Margen entre el mayor y el segundo: ", promedio_margen, "%")
print("\nProceso Terminado!\n")

# Agregando los Metadatos de Fecha, Hora y Tiempo de Ejecución
fecha_actual = datetime.datetime.now().strftime("%Y-%m-%d")
cadena = "Fecha: " + fecha_actual
metadatos.append(cadena)
hora_actual = datetime.datetime.now().strftime("%H:%M:%S.%f")
cadena = "Hora: " + hora_actual
metadatos.append(cadena)
cadena = "Tiempo: " + str(tiempo_total)   
metadatos.append(cadena)

# Agregando los Metadatos específicos de la prueba
cadena = "Total de registros analizados: " + str(total_registros)
metadatos.append(cadena)
cadena = "Promedio de Similitudes: " + str(promedio_similitudes) + "%"
metadatos.append(cadena)
cadena = "Cantidad de Aciertos: " + str(total_trues)
metadatos.append(cadena)
cadena = "Promedio del Margen entre el mayor y el segundo: " + str(promedio_margen) + "%"
metadatos.append(cadena)

# Generando archivo TXT
nombre_archivo_txt = str(fecha_actual) + "-" + str(hora_actual) + "_" + device + "_" + llm_model_name + "_" + test_name + ".txt"
generar_archivo_txt(metadatos, nombre_archivo_txt)

# Impresión de Metadatos
print("\nMetadatos Recolectados:\n\n", metadatos)
print("\nNombre del archivo generado: ", nombre_archivo_txt, "\n")