Compare commits
7 Commits
13ceda63ba
...
7afdbca03a
Author | SHA1 | Date |
---|---|---|
|
7afdbca03a | |
|
164667bc2f | |
|
ffc686e140 | |
|
b4959e772f | |
|
4a1b16117e | |
|
2cec16af0e | |
|
df6e40e68d |
|
@ -0,0 +1,9 @@
|
|||
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.development
|
||||
.env.test
|
||||
.env.production
|
|
@ -2,220 +2,132 @@
|
|||
|
||||
## Introducción
|
||||
|
||||
Esta guía explica cómo configurar y usar correctamente la función `load_configuration()` en scripts ubicados bajo el directorio `/backend`. La función carga configuraciones desde un archivo `script_config.json` ubicado en el mismo directorio que el script que la llama.
|
||||
Esta guía explica cómo usar la función `load_configuration()` para cargar parámetros desde un archivo `script_config.json` en los scripts del backend.
|
||||
|
||||
## Configuración del Path e Importación
|
||||
## 1. Configuración del Script
|
||||
|
||||
### 1. Configuración estándar del Path
|
||||
Para que tus scripts puedan encontrar los módulos del proyecto, necesitas añadir el directorio raíz al path de Python.
|
||||
|
||||
Para scripts ubicados en subdirectorios bajo `/backend`, usa este patrón estándar:
|
||||
### Path Setup e Importación
|
||||
|
||||
Coloca este código al inicio de tu script:
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Configurar el path al directorio raíz del proyecto
|
||||
# Añadir el directorio raíz al sys.path
|
||||
# El número de `os.path.dirname()` depende de la profundidad del script.
|
||||
# Para /backend/script_groups/grupo/script.py, son 4.
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
|
||||
# Importar la función de configuración
|
||||
# Importar la función
|
||||
from backend.script_utils import load_configuration
|
||||
```
|
||||
|
||||
**Nota:** El número de `os.path.dirname()` anidados depende de la profundidad del script:
|
||||
- Scripts en `/backend/script_groups/grupo/`: 4 niveles
|
||||
- Scripts en `/backend/`: 2 niveles
|
||||
## 2. Cargar la Configuración
|
||||
|
||||
### 2. Importación Correcta
|
||||
La función `load_configuration()` busca y carga un archivo llamado `script_config.json` que debe estar en el **mismo directorio** que el script que la ejecuta. Devuelve un diccionario con todo el contenido del JSON.
|
||||
|
||||
**✅ Correcto:**
|
||||
```python
|
||||
from backend.script_utils import load_configuration
|
||||
```
|
||||
|
||||
**❌ Incorrecto:**
|
||||
```python
|
||||
from script_utils import load_configuration # No funciona desde subdirectorios
|
||||
```
|
||||
|
||||
## Uso de la Función load_configuration()
|
||||
|
||||
### Implementación Básica
|
||||
### Ejemplo de Uso
|
||||
|
||||
```python
|
||||
def main():
|
||||
# Cargar configuraciones
|
||||
# Cargar configuraciones del archivo script_config.json
|
||||
configs = load_configuration()
|
||||
|
||||
# Obtener el directorio de trabajo
|
||||
working_directory = configs.get("working_directory", "")
|
||||
# Es buena práctica verificar si la configuración se cargó
|
||||
if not configs:
|
||||
print("Error: No se pudo cargar la configuración. Saliendo.")
|
||||
return
|
||||
|
||||
# Acceder a configuraciones por nivel
|
||||
# Acceder a los parámetros usando .get() para evitar errores
|
||||
working_directory = configs.get("working_directory", "")
|
||||
level1_config = configs.get("level1", {})
|
||||
level2_config = configs.get("level2", {})
|
||||
level3_config = configs.get("level3", {})
|
||||
|
||||
# Ejemplo de uso de parámetros específicos con valores por defecto
|
||||
# Ejemplo de uso de un parámetro específico con valor por defecto
|
||||
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
|
||||
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
|
||||
|
||||
print(f"Directorio de trabajo: {working_directory}")
|
||||
print(f"Directorio de salida SCL: {scl_output_dir}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### Estructura del Archivo script_config.json
|
||||
## 3. Archivo `script_config.json`
|
||||
|
||||
El archivo `script_config.json` debe estar ubicado en el mismo directorio que el script que llama a `load_configuration()`. Estructura recomendada:
|
||||
Este archivo contiene los parámetros de tu script. La estructura interna del JSON, como el uso de `"level1"`, `"level2"`, etc., es una convención para organizar los parámetros. `load_configuration()` simplemente lee el archivo y devuelve su contenido.
|
||||
|
||||
**Ejemplo de `script_config.json`:**
|
||||
```json
|
||||
{
|
||||
"working_directory": "/ruta/al/directorio/de/trabajo",
|
||||
"level1": {
|
||||
"parametro_global_1": "valor1",
|
||||
"parametro_global_2": "valor2"
|
||||
"parametro_global_1": "valor1"
|
||||
},
|
||||
"level2": {
|
||||
"scl_output_dir": "scl_output",
|
||||
"xref_output_dir": "xref_output",
|
||||
"xref_source_subdir": "source",
|
||||
"aggregated_filename": "full_project_representation.md"
|
||||
"xref_output_dir": "xref_output"
|
||||
},
|
||||
"level3": {
|
||||
"parametro_especifico_1": true,
|
||||
"parametro_especifico_2": 100
|
||||
"parametro_especifico_1": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ejemplo Completo de Implementación
|
||||
## 4. Manejo de Errores
|
||||
|
||||
```python
|
||||
"""
|
||||
Script de ejemplo que demuestra el uso completo de load_configuration()
|
||||
"""
|
||||
`load_configuration()` está diseñada para ser robusta:
|
||||
- Si `script_config.json` no se encuentra, retorna un diccionario vacío `{}`.
|
||||
- Si el JSON es inválido, imprime un error y también retorna `{}`.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
**Siempre** comprueba si el diccionario devuelto está vacío para manejar estos casos de forma segura en tu script.
|
||||
|
||||
# Configuración del path
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
from backend.script_utils import load_configuration
|
||||
## 5. Jerarquía de Archivos de Configuración
|
||||
|
||||
El sistema utiliza un modelo de configuración en cascada para gestionar los parámetros de los scripts. Esta jerarquía permite establecer configuraciones a nivel global, de grupo y de trabajo. Antes de la ejecución, el launcher lee estos archivos, los combina y genera un único `script_config.json` en la carpeta del script. La función `load_configuration()` es la que finalmente lee este archivo consolidado.
|
||||
|
||||
def main():
|
||||
print("=== Cargando Configuración ===")
|
||||
A continuación se describe la finalidad y ubicación de cada archivo clave.
|
||||
|
||||
# Cargar configuraciones
|
||||
configs = load_configuration()
|
||||
### Archivos de Valores (Parámetros)
|
||||
|
||||
# Verificar que se cargó correctamente
|
||||
if not configs:
|
||||
print("Error: No se pudo cargar la configuración")
|
||||
return
|
||||
Contienen los datos y variables que utilizará el script. La configuración se superpone en el siguiente orden: `Nivel 1 < Nivel 2 < Nivel 3`.
|
||||
|
||||
# Obtener configuraciones
|
||||
working_directory = configs.get("working_directory", "")
|
||||
level1_config = configs.get("level1", {})
|
||||
level2_config = configs.get("level2", {})
|
||||
level3_config = configs.get("level3", {})
|
||||
- **`data.json` (Nivel 1 - Global)**
|
||||
- **Ubicación:** `data/data.json`
|
||||
- **Utilidad:** Almacena variables globales disponibles para todos los scripts. Ideal para parámetros generales.
|
||||
- **Acceso:** Sus datos se cargan en la clave `"level1"` del diccionario `configs`.
|
||||
|
||||
# Mostrar configuraciones cargadas
|
||||
print(f"Directorio de trabajo: {working_directory}")
|
||||
print("Configuración Nivel 1:", json.dumps(level1_config, indent=2))
|
||||
print("Configuración Nivel 2:", json.dumps(level2_config, indent=2))
|
||||
print("Configuración Nivel 3:", json.dumps(level3_config, indent=2))
|
||||
- **`script_config.json` (Nivel 2 - Grupo)**
|
||||
- **Ubicación:** En la raíz de cada directorio de grupo (ej: `backend/script_groups/MiGrupo/script_config.json`).
|
||||
- **Utilidad:** Define parámetros compartidos por todos los scripts de un grupo.
|
||||
- **Acceso:** Sus datos se cargan en la clave `"level2"`.
|
||||
|
||||
# Ejemplo de uso de parámetros con valores por defecto
|
||||
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
|
||||
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
|
||||
- **`work_dir.json` (Nivel 3 - Directorio de Trabajo)**
|
||||
- **Ubicación:** Dentro del directorio de trabajo que el script va a procesar.
|
||||
- **Utilidad:** Contiene parámetros para una ejecución específica. Es el nivel más específico y sobrescribe los anteriores.
|
||||
- **Acceso:** Sus datos se cargan en la clave `"level3"`.
|
||||
|
||||
print(f"Directorio de salida SCL: {scl_output_dir}")
|
||||
print(f"Directorio de salida XREF: {xref_output_dir}")
|
||||
### Archivos de Esquema (Definiciones de Estructura)
|
||||
|
||||
No contienen valores, sino que describen la estructura y tipos de datos que los archivos de valores deben tener. Son usados por el launcher para validación y para generar interfaces de configuración.
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
- **`esquema_group.json`**
|
||||
- **Ubicación:** Raíz del directorio del grupo.
|
||||
- **Utilidad:** Define la estructura del `script_config.json` del grupo.
|
||||
|
||||
## Manejo de Errores
|
||||
- **`esquema_work.json`**
|
||||
- **Ubicación:** Raíz del directorio del grupo.
|
||||
- **Utilidad:** Define la estructura del `work_dir.json`.
|
||||
|
||||
La función `load_configuration()` maneja automáticamente los siguientes casos:
|
||||
|
||||
1. **Archivo no encontrado**: Retorna un diccionario vacío `{}`
|
||||
2. **JSON inválido**: Retorna un diccionario vacío y muestra un mensaje de error
|
||||
3. **Errores de lectura**: Retorna un diccionario vacío y muestra un mensaje de error
|
||||
|
||||
### Verificación de Configuración Válida
|
||||
|
||||
```python
|
||||
configs = load_configuration()
|
||||
|
||||
# Verificar que se cargó correctamente
|
||||
if not configs:
|
||||
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
|
||||
working_directory = "."
|
||||
else:
|
||||
working_directory = configs.get("working_directory", ".")
|
||||
|
||||
# Verificar directorio de trabajo
|
||||
if not os.path.exists(working_directory):
|
||||
print(f"Error: El directorio de trabajo no existe: {working_directory}")
|
||||
return
|
||||
```
|
||||
|
||||
## Mejores Prácticas
|
||||
|
||||
1. **Siempre proporciona valores por defecto** al usar `.get()`:
|
||||
```python
|
||||
valor = config.get("clave", "valor_por_defecto")
|
||||
```
|
||||
|
||||
2. **Verifica la existencia de directorios críticos**:
|
||||
```python
|
||||
if not os.path.exists(working_directory):
|
||||
print(f"Error: Directorio no encontrado: {working_directory}")
|
||||
return
|
||||
```
|
||||
|
||||
3. **Documenta los parámetros esperados** en tu script:
|
||||
```python
|
||||
# Parámetros esperados en level2:
|
||||
# - scl_output_dir: Directorio de salida para archivos SCL
|
||||
# - xref_output_dir: Directorio de salida para referencias cruzadas
|
||||
```
|
||||
|
||||
4. **Usa nombres de parámetros consistentes** en todos los scripts del mismo grupo.
|
||||
|
||||
## Definición Técnica de load_configuration()
|
||||
|
||||
```python
|
||||
def load_configuration() -> Dict[str, Any]:
|
||||
"""
|
||||
Load configuration from script_config.json in the current script directory.
|
||||
|
||||
Returns:
|
||||
Dict containing configurations with levels 1, 2, 3 and working_directory
|
||||
|
||||
Example usage in scripts:
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
configs = load_configuration()
|
||||
level1_config = configs.get("level1", {})
|
||||
level2_config = configs.get("level2", {})
|
||||
level3_config = configs.get("level3", {})
|
||||
working_dir = configs.get("working_directory", "")
|
||||
"""
|
||||
```
|
||||
|
||||
La función utiliza `inspect.stack()` para determinar automáticamente el directorio del script que la llama, asegurando que siempre busque el archivo `script_config.json` en la ubicación correcta.
|
||||
|
||||
## Documentación de Scripts para el Launcher
|
||||
## 6. Documentación de Scripts para el Launcher
|
||||
|
||||
El sistema de launcher utiliza archivos JSON para mostrar información sobre los grupos de scripts y scripts individuales en la interfaz web.
|
||||
|
||||
|
@ -291,3 +203,208 @@ Para un grupo "XML Parser to SCL":
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Requisitos de Codificación de Salida (stdout)
|
||||
|
||||
Toda la salida estándar (`stdout`) generada por los scripts (por ejemplo, mediante la función `print()`) es capturada en tiempo real y mostrada en el panel de logs del frontend.
|
||||
|
||||
Para garantizar que el texto se muestre correctamente y evitar caracteres corruptos (mojibake), **la salida de los scripts debe tener codificación UTF-8**.
|
||||
|
||||
### Configuración Automática
|
||||
|
||||
El sistema está diseñado para facilitar esto. Al ejecutar un script, el entorno se configura automáticamente para que la salida de Python sea UTF-8. Específicamente, se establece la variable de entorno `PYTHONIOENCODING=utf-8`.
|
||||
|
||||
Gracias a esto, en la mayoría de los casos, **no necesitas hacer nada especial**. Simplemente usa `print()` y la codificación será la correcta.
|
||||
|
||||
### Casos Especiales y Solución de Problemas
|
||||
|
||||
Pueden surgir problemas si tu script lee datos de fuentes externas (como archivos) que tienen una codificación diferente. Si imprimes contenido directamente sin decodificarlo primero, podrías enviar bytes no válidos a la salida.
|
||||
|
||||
La regla es: **decodifica los datos de entrada a un string de Python lo antes posible, y deja que `print()` se encargue de la codificación de salida.**
|
||||
|
||||
**Ejemplo de cómo manejar un archivo con codificación `latin-1`:**
|
||||
|
||||
```python
|
||||
# INCORRECTO: Si el archivo no es UTF-8, esto puede enviar bytes inválidos.
|
||||
# El launcher intentará decodificarlos como UTF-8 y podría fallar o mostrar basura.
|
||||
try:
|
||||
with open('mi_archivo_legacy.txt', 'rb') as f:
|
||||
print(f.read())
|
||||
except Exception as e:
|
||||
print(f"Esto probablemente falle o muestre texto corrupto: {e}")
|
||||
|
||||
|
||||
# CORRECTO: Leer el archivo especificando su codificación para decodificarlo a un string.
|
||||
# Una vez que es un string de Python, `print` lo manejará correctamente.
|
||||
try:
|
||||
with open('mi_archivo_legacy.txt', 'r', encoding='latin-1') as f:
|
||||
contenido = f.read()
|
||||
# Ahora `contenido` es un string de Python.
|
||||
# print() lo codificará a UTF-8 automáticamente gracias a la configuración del entorno.
|
||||
print(contenido)
|
||||
except FileNotFoundError:
|
||||
print("El archivo 'mi_archivo_legacy.txt' no fue encontrado para el ejemplo.")
|
||||
except Exception as e:
|
||||
print(f"Error al procesar el archivo: {e}")
|
||||
```
|
||||
|
||||
## 8. Uso de Servicios Compartidos
|
||||
|
||||
El proyecto ofrece una serie de servicios reutilizables en el directorio `services/` para tareas comunes como la manipulación de archivos Excel, detección de idioma o traducción.
|
||||
|
||||
Para utilizar estos servicios en tu script, asegúrate de que el directorio raíz del proyecto esté en el `sys.path`, como se explica en la sección 1 de esta guía.
|
||||
|
||||
### 8.1 Servicio de Excel (`ExcelService`)
|
||||
|
||||
El `ExcelService` (`services/excel/excel_service.py`) facilita la lectura y escritura de archivos Excel, con manejo de reintentos (por si el archivo está abierto) y opciones de formato.
|
||||
|
||||
**Ejemplo de importación y uso:**
|
||||
|
||||
```python
|
||||
# Asegúrate de tener el path raíz configurado
|
||||
# ... (código de configuración de sys.path)
|
||||
|
||||
from services.excel.excel_service import ExcelService
|
||||
|
||||
def main():
|
||||
excel_service = ExcelService()
|
||||
|
||||
# Leer un archivo Excel
|
||||
try:
|
||||
df = excel_service.read_excel("mi_archivo_de_entrada.xlsx")
|
||||
print("Datos cargados exitosamente.")
|
||||
|
||||
# ... procesar el DataFrame ...
|
||||
|
||||
# Guardar el DataFrame con formato personalizado
|
||||
format_options = {
|
||||
'freeze_row': 2,
|
||||
'header_color': 'E6E6E6'
|
||||
}
|
||||
|
||||
excel_service.save_excel(
|
||||
df,
|
||||
"mi_archivo_de_salida.xlsx",
|
||||
sheet_name="Resultados",
|
||||
format_options=format_options
|
||||
)
|
||||
print("Archivo guardado con éxito.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ocurrió un error al manejar el archivo Excel: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### 8.2 Servicios de Lenguaje
|
||||
|
||||
Los servicios de lenguaje (`services/language/`) permiten detectar el idioma de un texto.
|
||||
|
||||
**Ejemplo de importación y uso:**
|
||||
|
||||
```python
|
||||
# Asegúrate de tener el path raíz configurado
|
||||
# ... (código de configuración de sys.path)
|
||||
|
||||
from services.language.language_factory import LanguageFactory
|
||||
from services.language.language_utils import LanguageUtils
|
||||
|
||||
def main():
|
||||
# Crear el servicio de detección de idioma
|
||||
allowed_languages = LanguageUtils.get_available_languages()
|
||||
detector = LanguageFactory.create_service("langid", allowed_languages=allowed_languages)
|
||||
|
||||
# Detectar idioma de un texto
|
||||
text = "Este es un texto de ejemplo en español."
|
||||
lang, confidence = detector.detect_language(text)
|
||||
|
||||
print(f"Texto: '{text}'")
|
||||
print(f"Idioma detectado: {LanguageUtils.get_language_name(lang)} (código: {lang})")
|
||||
print(f"Confianza: {confidence:.2f}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### 8.3 Servicios de LLM (Modelos de Lenguaje Grandes)
|
||||
|
||||
El proyecto integra una fábrica de servicios (`LLMFactory`) para interactuar con diferentes Modelos de Lenguaje Grandes (LLMs). Esto permite a los scripts aprovechar la IA generativa para tareas como el análisis de código, la generación de descripciones semánticas, etc.
|
||||
|
||||
#### 8.3.1 Configuración de API Keys
|
||||
|
||||
La mayoría de los servicios de LLM requieren una clave de API para funcionar. El sistema gestiona esto de forma centralizada a través de variables de entorno.
|
||||
|
||||
1. **Crear el archivo `.env`**: En el **directorio raíz del proyecto**, crea un archivo llamado `.env` (si aún no existe).
|
||||
|
||||
2. **Añadir las API Keys**: Abre el archivo `.env` y añade las claves para los servicios que planeas utilizar. El sistema cargará estas variables automáticamente al inicio.
|
||||
|
||||
```env
|
||||
# Ejemplo de contenido para el archivo .env
|
||||
# (Solo necesitas añadir las claves de los servicios que vayas a usar)
|
||||
|
||||
OPENAI_API_KEY="sk-..."
|
||||
GROQ_API_KEY="gsk_..."
|
||||
CLAUDE_API_KEY="sk-ant-..."
|
||||
GEMINI_API_KEY="AIzaSy..."
|
||||
GROK_API_KEY="TU_API_KEY_DE_GROK"
|
||||
```
|
||||
|
||||
**Nota**: El servicio `ollama` se ejecuta localmente y no requiere una clave de API.
|
||||
|
||||
#### 8.3.2 Ejemplo de importación y uso
|
||||
|
||||
El siguiente ejemplo muestra cómo un script puede cargar su configuración, inicializar un servicio de LLM y usarlo para generar texto. Este patrón es similar al utilizado en `x3_generate_semantic_descriptions.py`.
|
||||
|
||||
```python
|
||||
# Asegúrate de tener el path raíz configurado
|
||||
# ... (código de configuración de sys.path)
|
||||
|
||||
from services.llm.llm_factory import LLMFactory
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
def main():
|
||||
# Cargar la configuración del script, que puede incluir qué LLM usar
|
||||
configs = load_configuration()
|
||||
llm_configs = configs.get("llm", {})
|
||||
|
||||
# Obtener el tipo de servicio y otros parámetros del config
|
||||
# Por defecto, usamos 'groq' si no se especifica
|
||||
service_type = llm_configs.get("service", "groq")
|
||||
|
||||
print(f"🤖 Inicializando servicio LLM: {service_type}")
|
||||
|
||||
# Crear una instancia del servicio usando la fábrica
|
||||
# La fábrica se encarga de pasar las API keys desde las variables de entorno
|
||||
llm_service = LLMFactory.create_service(service_type, **llm_configs)
|
||||
|
||||
if not llm_service:
|
||||
print(f"❌ Error: No se pudo crear el servicio LLM '{service_type}'. Abortando.")
|
||||
return
|
||||
|
||||
# Usar el servicio para generar texto
|
||||
try:
|
||||
prompt = "Explica la computación cuántica en una sola frase."
|
||||
print(f"Enviando prompt: '{prompt}'")
|
||||
|
||||
description = llm_service.generate_text(prompt)
|
||||
|
||||
print("\nRespuesta del LLM:")
|
||||
print(description)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ocurrió un error al contactar al servicio LLM: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
#### 8.3.3 Servicios Disponibles
|
||||
|
||||
La `LLMFactory` soporta los siguientes tipos de servicio (`service_type`):
|
||||
- `openai`
|
||||
- `groq`
|
||||
- `claude`
|
||||
- `gemini`
|
||||
- `grok`
|
||||
- `ollama` (para ejecución local)
|
85
app.py
85
app.py
|
@ -10,7 +10,8 @@ from datetime import datetime
|
|||
import time # Added for shutdown delay
|
||||
import sys # Added for platform detection
|
||||
import subprocess # Add this to the imports at the top
|
||||
import shutil # For shutil.whichimport os
|
||||
import shutil # For shutil.which
|
||||
import glob # For finding workspace files
|
||||
|
||||
# --- Imports for System Tray Icon ---
|
||||
import threading
|
||||
|
@ -993,6 +994,29 @@ def get_all_csharp_executables(project_id):
|
|||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/csharp-solution-file/<project_id>", methods=["GET"])
|
||||
def get_csharp_solution_file(project_id):
|
||||
"""Obtener archivo de solución (.sln) del proyecto C#"""
|
||||
try:
|
||||
solution_file = csharp_launcher_manager.find_solution_file(project_id)
|
||||
if solution_file:
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"solution_file": solution_file,
|
||||
"exists": True
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"solution_file": None,
|
||||
"exists": False
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Error buscando archivo de solución: {str(e)}"
|
||||
}), 500
|
||||
|
||||
# === FIN C# LAUNCHER APIs ===
|
||||
|
||||
|
||||
|
@ -1188,7 +1212,43 @@ def get_python_markdown_content(project_id, relative_path):
|
|||
|
||||
# === FIN PYTHON LAUNCHER APIs ===
|
||||
|
||||
# --- Helper function to find VS Code ---
|
||||
# --- Helper functions ---
|
||||
def find_workspace_file(directory, editor):
|
||||
"""
|
||||
Busca archivos de workspace específicos en un directorio.
|
||||
|
||||
Args:
|
||||
directory (str): Directorio donde buscar
|
||||
editor (str): Editor ('vscode' o 'cursor')
|
||||
|
||||
Returns:
|
||||
str: Ruta del archivo de workspace encontrado, o None si no hay
|
||||
"""
|
||||
if not os.path.isdir(directory):
|
||||
return None
|
||||
|
||||
# Definir extensiones de archivo según el editor
|
||||
if editor == 'vscode':
|
||||
extensions = ['.code-workspace']
|
||||
elif editor == 'cursor':
|
||||
# Cursor puede usar tanto .cursor-workspace como .code-workspace
|
||||
extensions = ['.cursor-workspace', '.code-workspace']
|
||||
else:
|
||||
return None
|
||||
|
||||
# Buscar archivos con las extensiones apropiadas
|
||||
for ext in extensions:
|
||||
pattern = os.path.join(directory, f"*{ext}")
|
||||
workspace_files = glob.glob(pattern)
|
||||
|
||||
if workspace_files:
|
||||
# Si hay múltiples, tomar el primero (o se podría implementar lógica más sofisticada)
|
||||
workspace_file = workspace_files[0]
|
||||
print(f"Found {editor} workspace: {workspace_file}")
|
||||
return workspace_file
|
||||
|
||||
return None
|
||||
|
||||
def find_vscode_executable():
|
||||
"""Intenta encontrar el ejecutable de VS Code en ubicaciones comunes y en el PATH."""
|
||||
# Comprobar la variable de entorno VSCODE_PATH primero (si la defines)
|
||||
|
@ -1332,13 +1392,30 @@ def open_group_in_editor(editor, group_system, group_id):
|
|||
print(f"Launching {editor_name} from: {editor_path}")
|
||||
print(f"Opening directory: {script_group_path}")
|
||||
|
||||
# Buscar archivos de workspace específicos o archivos de solución
|
||||
target_to_open = script_group_path
|
||||
|
||||
# Para VSCode y Cursor, buscar archivos de workspace
|
||||
if editor in ['vscode', 'cursor']:
|
||||
workspace_file = find_workspace_file(script_group_path, editor)
|
||||
if workspace_file:
|
||||
target_to_open = workspace_file
|
||||
print(f"Found {editor} workspace file: {workspace_file}")
|
||||
|
||||
# Para Visual Studio 2022 y proyectos C#, intentar abrir archivo .sln específico
|
||||
elif editor == 'vs2022' and group_system == 'csharp':
|
||||
solution_file = csharp_launcher_manager.find_solution_file(group_id)
|
||||
if solution_file:
|
||||
target_to_open = solution_file
|
||||
print(f"Found solution file: {solution_file}")
|
||||
|
||||
# Ejecutar el editor
|
||||
process = subprocess.Popen(f'"{editor_path}" "{script_group_path}"', shell=True)
|
||||
process = subprocess.Popen(f'"{editor_path}" "{target_to_open}"', shell=True)
|
||||
print(f"{editor_name} process started with PID: {process.pid}")
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"message": f"{editor_name} abierto en: {script_group_path}"
|
||||
"message": f"{editor_name} abierto en: {target_to_open}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
(* Código SCL generado desde LAD TwinCAT *)\n(* Convertidor mejorado con SymPy - Estructura DNF preferida *)\n(* Path original: *)\n\nPROGRAM \n\n (* Código LAD convertido a SCL *)\n\n\nEND_PROGRAM
|
File diff suppressed because it is too large
Load Diff
|
@ -2,220 +2,132 @@
|
|||
|
||||
## Introducción
|
||||
|
||||
Esta guía explica cómo configurar y usar correctamente la función `load_configuration()` en scripts ubicados bajo el directorio `/backend`. La función carga configuraciones desde un archivo `script_config.json` ubicado en el mismo directorio que el script que la llama.
|
||||
Esta guía explica cómo usar la función `load_configuration()` para cargar parámetros desde un archivo `script_config.json` en los scripts del backend.
|
||||
|
||||
## Configuración del Path e Importación
|
||||
## 1. Configuración del Script
|
||||
|
||||
### 1. Configuración estándar del Path
|
||||
Para que tus scripts puedan encontrar los módulos del proyecto, necesitas añadir el directorio raíz al path de Python.
|
||||
|
||||
Para scripts ubicados en subdirectorios bajo `/backend`, usa este patrón estándar:
|
||||
### Path Setup e Importación
|
||||
|
||||
Coloca este código al inicio de tu script:
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Configurar el path al directorio raíz del proyecto
|
||||
# Añadir el directorio raíz al sys.path
|
||||
# El número de `os.path.dirname()` depende de la profundidad del script.
|
||||
# Para /backend/script_groups/grupo/script.py, son 4.
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
|
||||
# Importar la función de configuración
|
||||
# Importar la función
|
||||
from backend.script_utils import load_configuration
|
||||
```
|
||||
|
||||
**Nota:** El número de `os.path.dirname()` anidados depende de la profundidad del script:
|
||||
- Scripts en `/backend/script_groups/grupo/`: 4 niveles
|
||||
- Scripts en `/backend/`: 2 niveles
|
||||
## 2. Cargar la Configuración
|
||||
|
||||
### 2. Importación Correcta
|
||||
La función `load_configuration()` busca y carga un archivo llamado `script_config.json` que debe estar en el **mismo directorio** que el script que la ejecuta. Devuelve un diccionario con todo el contenido del JSON.
|
||||
|
||||
**✅ Correcto:**
|
||||
```python
|
||||
from backend.script_utils import load_configuration
|
||||
```
|
||||
|
||||
**❌ Incorrecto:**
|
||||
```python
|
||||
from script_utils import load_configuration # No funciona desde subdirectorios
|
||||
```
|
||||
|
||||
## Uso de la Función load_configuration()
|
||||
|
||||
### Implementación Básica
|
||||
### Ejemplo de Uso
|
||||
|
||||
```python
|
||||
def main():
|
||||
# Cargar configuraciones
|
||||
# Cargar configuraciones del archivo script_config.json
|
||||
configs = load_configuration()
|
||||
|
||||
# Obtener el directorio de trabajo
|
||||
working_directory = configs.get("working_directory", "")
|
||||
# Es buena práctica verificar si la configuración se cargó
|
||||
if not configs:
|
||||
print("Error: No se pudo cargar la configuración. Saliendo.")
|
||||
return
|
||||
|
||||
# Acceder a configuraciones por nivel
|
||||
# Acceder a los parámetros usando .get() para evitar errores
|
||||
working_directory = configs.get("working_directory", "")
|
||||
level1_config = configs.get("level1", {})
|
||||
level2_config = configs.get("level2", {})
|
||||
level3_config = configs.get("level3", {})
|
||||
|
||||
# Ejemplo de uso de parámetros específicos con valores por defecto
|
||||
# Ejemplo de uso de un parámetro específico con valor por defecto
|
||||
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
|
||||
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
|
||||
|
||||
print(f"Directorio de trabajo: {working_directory}")
|
||||
print(f"Directorio de salida SCL: {scl_output_dir}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### Estructura del Archivo script_config.json
|
||||
## 3. Archivo `script_config.json`
|
||||
|
||||
El archivo `script_config.json` debe estar ubicado en el mismo directorio que el script que llama a `load_configuration()`. Estructura recomendada:
|
||||
Este archivo contiene los parámetros de tu script. La estructura interna del JSON, como el uso de `"level1"`, `"level2"`, etc., es una convención para organizar los parámetros. `load_configuration()` simplemente lee el archivo y devuelve su contenido.
|
||||
|
||||
**Ejemplo de `script_config.json`:**
|
||||
```json
|
||||
{
|
||||
"working_directory": "/ruta/al/directorio/de/trabajo",
|
||||
"level1": {
|
||||
"parametro_global_1": "valor1",
|
||||
"parametro_global_2": "valor2"
|
||||
"parametro_global_1": "valor1"
|
||||
},
|
||||
"level2": {
|
||||
"scl_output_dir": "scl_output",
|
||||
"xref_output_dir": "xref_output",
|
||||
"xref_source_subdir": "source",
|
||||
"aggregated_filename": "full_project_representation.md"
|
||||
"xref_output_dir": "xref_output"
|
||||
},
|
||||
"level3": {
|
||||
"parametro_especifico_1": true,
|
||||
"parametro_especifico_2": 100
|
||||
"parametro_especifico_1": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ejemplo Completo de Implementación
|
||||
## 4. Manejo de Errores
|
||||
|
||||
```python
|
||||
"""
|
||||
Script de ejemplo que demuestra el uso completo de load_configuration()
|
||||
"""
|
||||
`load_configuration()` está diseñada para ser robusta:
|
||||
- Si `script_config.json` no se encuentra, retorna un diccionario vacío `{}`.
|
||||
- Si el JSON es inválido, imprime un error y también retorna `{}`.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
**Siempre** comprueba si el diccionario devuelto está vacío para manejar estos casos de forma segura en tu script.
|
||||
|
||||
# Configuración del path
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
from backend.script_utils import load_configuration
|
||||
## 5. Jerarquía de Archivos de Configuración
|
||||
|
||||
El sistema utiliza un modelo de configuración en cascada para gestionar los parámetros de los scripts. Esta jerarquía permite establecer configuraciones a nivel global, de grupo y de trabajo. Antes de la ejecución, el launcher lee estos archivos, los combina y genera un único `script_config.json` en la carpeta del script. La función `load_configuration()` es la que finalmente lee este archivo consolidado.
|
||||
|
||||
def main():
|
||||
print("=== Cargando Configuración ===")
|
||||
A continuación se describe la finalidad y ubicación de cada archivo clave.
|
||||
|
||||
# Cargar configuraciones
|
||||
configs = load_configuration()
|
||||
### Archivos de Valores (Parámetros)
|
||||
|
||||
# Verificar que se cargó correctamente
|
||||
if not configs:
|
||||
print("Error: No se pudo cargar la configuración")
|
||||
return
|
||||
Contienen los datos y variables que utilizará el script. La configuración se superpone en el siguiente orden: `Nivel 1 < Nivel 2 < Nivel 3`.
|
||||
|
||||
# Obtener configuraciones
|
||||
working_directory = configs.get("working_directory", "")
|
||||
level1_config = configs.get("level1", {})
|
||||
level2_config = configs.get("level2", {})
|
||||
level3_config = configs.get("level3", {})
|
||||
- **`data.json` (Nivel 1 - Global)**
|
||||
- **Ubicación:** `data/data.json`
|
||||
- **Utilidad:** Almacena variables globales disponibles para todos los scripts. Ideal para parámetros generales.
|
||||
- **Acceso:** Sus datos se cargan en la clave `"level1"` del diccionario `configs`.
|
||||
|
||||
# Mostrar configuraciones cargadas
|
||||
print(f"Directorio de trabajo: {working_directory}")
|
||||
print("Configuración Nivel 1:", json.dumps(level1_config, indent=2))
|
||||
print("Configuración Nivel 2:", json.dumps(level2_config, indent=2))
|
||||
print("Configuración Nivel 3:", json.dumps(level3_config, indent=2))
|
||||
- **`script_config.json` (Nivel 2 - Grupo)**
|
||||
- **Ubicación:** En la raíz de cada directorio de grupo (ej: `backend/script_groups/MiGrupo/script_config.json`).
|
||||
- **Utilidad:** Define parámetros compartidos por todos los scripts de un grupo.
|
||||
- **Acceso:** Sus datos se cargan en la clave `"level2"`.
|
||||
|
||||
# Ejemplo de uso de parámetros con valores por defecto
|
||||
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
|
||||
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
|
||||
- **`work_dir.json` (Nivel 3 - Directorio de Trabajo)**
|
||||
- **Ubicación:** Dentro del directorio de trabajo que el script va a procesar.
|
||||
- **Utilidad:** Contiene parámetros para una ejecución específica. Es el nivel más específico y sobrescribe los anteriores.
|
||||
- **Acceso:** Sus datos se cargan en la clave `"level3"`.
|
||||
|
||||
print(f"Directorio de salida SCL: {scl_output_dir}")
|
||||
print(f"Directorio de salida XREF: {xref_output_dir}")
|
||||
### Archivos de Esquema (Definiciones de Estructura)
|
||||
|
||||
No contienen valores, sino que describen la estructura y tipos de datos que los archivos de valores deben tener. Son usados por el launcher para validación y para generar interfaces de configuración.
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
- **`esquema_group.json`**
|
||||
- **Ubicación:** Raíz del directorio del grupo.
|
||||
- **Utilidad:** Define la estructura del `script_config.json` del grupo.
|
||||
|
||||
## Manejo de Errores
|
||||
- **`esquema_work.json`**
|
||||
- **Ubicación:** Raíz del directorio del grupo.
|
||||
- **Utilidad:** Define la estructura del `work_dir.json`.
|
||||
|
||||
La función `load_configuration()` maneja automáticamente los siguientes casos:
|
||||
|
||||
1. **Archivo no encontrado**: Retorna un diccionario vacío `{}`
|
||||
2. **JSON inválido**: Retorna un diccionario vacío y muestra un mensaje de error
|
||||
3. **Errores de lectura**: Retorna un diccionario vacío y muestra un mensaje de error
|
||||
|
||||
### Verificación de Configuración Válida
|
||||
|
||||
```python
|
||||
configs = load_configuration()
|
||||
|
||||
# Verificar que se cargó correctamente
|
||||
if not configs:
|
||||
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
|
||||
working_directory = "."
|
||||
else:
|
||||
working_directory = configs.get("working_directory", ".")
|
||||
|
||||
# Verificar directorio de trabajo
|
||||
if not os.path.exists(working_directory):
|
||||
print(f"Error: El directorio de trabajo no existe: {working_directory}")
|
||||
return
|
||||
```
|
||||
|
||||
## Mejores Prácticas
|
||||
|
||||
1. **Siempre proporciona valores por defecto** al usar `.get()`:
|
||||
```python
|
||||
valor = config.get("clave", "valor_por_defecto")
|
||||
```
|
||||
|
||||
2. **Verifica la existencia de directorios críticos**:
|
||||
```python
|
||||
if not os.path.exists(working_directory):
|
||||
print(f"Error: Directorio no encontrado: {working_directory}")
|
||||
return
|
||||
```
|
||||
|
||||
3. **Documenta los parámetros esperados** en tu script:
|
||||
```python
|
||||
# Parámetros esperados en level2:
|
||||
# - scl_output_dir: Directorio de salida para archivos SCL
|
||||
# - xref_output_dir: Directorio de salida para referencias cruzadas
|
||||
```
|
||||
|
||||
4. **Usa nombres de parámetros consistentes** en todos los scripts del mismo grupo.
|
||||
|
||||
## Definición Técnica de load_configuration()
|
||||
|
||||
```python
|
||||
def load_configuration() -> Dict[str, Any]:
|
||||
"""
|
||||
Load configuration from script_config.json in the current script directory.
|
||||
|
||||
Returns:
|
||||
Dict containing configurations with levels 1, 2, 3 and working_directory
|
||||
|
||||
Example usage in scripts:
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
configs = load_configuration()
|
||||
level1_config = configs.get("level1", {})
|
||||
level2_config = configs.get("level2", {})
|
||||
level3_config = configs.get("level3", {})
|
||||
working_dir = configs.get("working_directory", "")
|
||||
"""
|
||||
```
|
||||
|
||||
La función utiliza `inspect.stack()` para determinar automáticamente el directorio del script que la llama, asegurando que siempre busque el archivo `script_config.json` en la ubicación correcta.
|
||||
|
||||
## Documentación de Scripts para el Launcher
|
||||
## 6. Documentación de Scripts para el Launcher
|
||||
|
||||
El sistema de launcher utiliza archivos JSON para mostrar información sobre los grupos de scripts y scripts individuales en la interfaz web.
|
||||
|
||||
|
@ -291,3 +203,126 @@ Para un grupo "XML Parser to SCL":
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Requisitos de Codificación de Salida (stdout)
|
||||
|
||||
Toda la salida estándar (`stdout`) generada por los scripts (por ejemplo, mediante la función `print()`) es capturada en tiempo real y mostrada en el panel de logs del frontend.
|
||||
|
||||
Para garantizar que el texto se muestre correctamente y evitar caracteres corruptos (mojibake), **la salida de los scripts debe tener codificación UTF-8**.
|
||||
|
||||
### Configuración Automática
|
||||
|
||||
El sistema está diseñado para facilitar esto. Al ejecutar un script, el entorno se configura automáticamente para que la salida de Python sea UTF-8. Específicamente, se establece la variable de entorno `PYTHONIOENCODING=utf-8`.
|
||||
|
||||
Gracias a esto, en la mayoría de los casos, **no necesitas hacer nada especial**. Simplemente usa `print()` y la codificación será la correcta.
|
||||
|
||||
### Casos Especiales y Solución de Problemas
|
||||
|
||||
Pueden surgir problemas si tu script lee datos de fuentes externas (como archivos) que tienen una codificación diferente. Si imprimes contenido directamente sin decodificarlo primero, podrías enviar bytes no válidos a la salida.
|
||||
|
||||
La regla es: **decodifica los datos de entrada a un string de Python lo antes posible, y deja que `print()` se encargue de la codificación de salida.**
|
||||
|
||||
**Ejemplo de cómo manejar un archivo con codificación `latin-1`:**
|
||||
|
||||
```python
|
||||
# INCORRECTO: Si el archivo no es UTF-8, esto puede enviar bytes inválidos.
|
||||
# El launcher intentará decodificarlos como UTF-8 y podría fallar o mostrar basura.
|
||||
try:
|
||||
with open('mi_archivo_legacy.txt', 'rb') as f:
|
||||
print(f.read())
|
||||
except Exception as e:
|
||||
print(f"Esto probablemente falle o muestre texto corrupto: {e}")
|
||||
|
||||
|
||||
# CORRECTO: Leer el archivo especificando su codificación para decodificarlo a un string.
|
||||
# Una vez que es un string de Python, `print` lo manejará correctamente.
|
||||
try:
|
||||
with open('mi_archivo_legacy.txt', 'r', encoding='latin-1') as f:
|
||||
contenido = f.read()
|
||||
# Ahora `contenido` es un string de Python.
|
||||
# print() lo codificará a UTF-8 automáticamente gracias a la configuración del entorno.
|
||||
print(contenido)
|
||||
except FileNotFoundError:
|
||||
print("El archivo 'mi_archivo_legacy.txt' no fue encontrado para el ejemplo.")
|
||||
except Exception as e:
|
||||
print(f"Error al procesar el archivo: {e}")
|
||||
```
|
||||
|
||||
## 8. Uso de Servicios Compartidos
|
||||
|
||||
El proyecto ofrece una serie de servicios reutilizables en el directorio `services/` para tareas comunes como la manipulación de archivos Excel, detección de idioma o traducción.
|
||||
|
||||
Para utilizar estos servicios en tu script, asegúrate de que el directorio raíz del proyecto esté en el `sys.path`, como se explica en la sección 1 de esta guía.
|
||||
|
||||
### 8.1 Servicio de Excel (`ExcelService`)
|
||||
|
||||
El `ExcelService` (`services/excel/excel_service.py`) facilita la lectura y escritura de archivos Excel, con manejo de reintentos (por si el archivo está abierto) y opciones de formato.
|
||||
|
||||
**Ejemplo de importación y uso:**
|
||||
|
||||
```python
|
||||
# Asegúrate de tener el path raíz configurado
|
||||
# ... (código de configuración de sys.path)
|
||||
|
||||
from services.excel.excel_service import ExcelService
|
||||
|
||||
def main():
|
||||
excel_service = ExcelService()
|
||||
|
||||
# Leer un archivo Excel
|
||||
try:
|
||||
df = excel_service.read_excel("mi_archivo_de_entrada.xlsx")
|
||||
print("Datos cargados exitosamente.")
|
||||
|
||||
# ... procesar el DataFrame ...
|
||||
|
||||
# Guardar el DataFrame con formato personalizado
|
||||
format_options = {
|
||||
'freeze_row': 2,
|
||||
'header_color': 'E6E6E6'
|
||||
}
|
||||
|
||||
excel_service.save_excel(
|
||||
df,
|
||||
"mi_archivo_de_salida.xlsx",
|
||||
sheet_name="Resultados",
|
||||
format_options=format_options
|
||||
)
|
||||
print("Archivo guardado con éxito.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ocurrió un error al manejar el archivo Excel: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### 8.2 Servicios de Lenguaje
|
||||
|
||||
Los servicios de lenguaje (`services/language/`) permiten detectar el idioma de un texto.
|
||||
|
||||
**Ejemplo de importación y uso:**
|
||||
|
||||
```python
|
||||
# Asegúrate de tener el path raíz configurado
|
||||
# ... (código de configuración de sys.path)
|
||||
|
||||
from services.language.language_factory import LanguageFactory
|
||||
from services.language.language_utils import LanguageUtils
|
||||
|
||||
def main():
|
||||
# Crear el servicio de detección de idioma
|
||||
allowed_languages = LanguageUtils.get_available_languages()
|
||||
detector = LanguageFactory.create_service("langid", allowed_languages=allowed_languages)
|
||||
|
||||
# Detectar idioma de un texto
|
||||
text = "Este es un texto de ejemplo en español."
|
||||
lang, confidence = detector.detect_language(text)
|
||||
|
||||
print(f"Texto: '{text}'")
|
||||
print(f"Idioma detectado: {LanguageUtils.get_language_name(lang)} (código: {lang})")
|
||||
print(f"Confianza: {confidence:.2f}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
|
@ -5,6 +5,9 @@
|
|||
},
|
||||
{
|
||||
"path": "C:/Trabajo/SIDEL/13 - E5.007560 - Modifica O&U - SAE235/Reporte/Analisis"
|
||||
},
|
||||
{
|
||||
"path": "C:/Trabajo/SIDEL/13 - E5.007560 - Modifica O&U - SAE235/Reporte/ExportTwinCat"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
# Convertidor LAD a Pseudocódigo Estructurado
|
||||
|
||||
## Descripción
|
||||
|
||||
Este proyecto proporciona herramientas para convertir código LAD (Ladder Diagram) de TwinCAT a pseudocódigo estructurado compatible con IEC61131-3. El convertidor mantiene la semántica original del código LAD mientras lo transforma a un formato más legible y estructurado.
|
||||
|
||||
## Características
|
||||
|
||||
- ✅ **Parsing completo de LAD**: Analiza la estructura completa de archivos `.EXP` de TwinCAT
|
||||
- ✅ **Conversión semántica**: Mantiene la lógica original del diagrama ladder
|
||||
- ✅ **Formato estructurado**: Genera código pseudo estructurado con IF-THEN-END_IF
|
||||
- ✅ **Manejo de contactos**: Convierte contactos normales y negados correctamente
|
||||
- ✅ **Function blocks**: Identifica y convierte llamadas a bloques de función
|
||||
- ✅ **Operadores matemáticos**: Maneja operaciones aritméticas y lógicas
|
||||
- ✅ **Comentarios**: Preserva comentarios de las redes originales
|
||||
|
||||
## Archivos del Proyecto
|
||||
|
||||
### Convertidores Principales
|
||||
|
||||
1. **`simple_lad_converter.py`** - Convertidor simplificado y robusto (recomendado)
|
||||
2. **`lad_to_pseudocode_converter.py`** - Convertidor básico inicial
|
||||
3. **`lad_to_pseudocode_converter_enhanced.py`** - Versión avanzada con más características
|
||||
|
||||
### Archivos de Prueba
|
||||
|
||||
- **`test_simple.py`** - Script de prueba para el convertidor simple
|
||||
- **`ejemplo_conversion.py`** - Ejemplo de uso del convertidor básico
|
||||
- **`test_enhanced_converter.py`** - Prueba para el convertidor avanzado
|
||||
|
||||
## Uso Rápido
|
||||
|
||||
### Método Simple (Recomendado)
|
||||
|
||||
```bash
|
||||
python test_simple.py
|
||||
```
|
||||
|
||||
Este comando procesará el archivo `.example/INPUT.EXP` y generará `output_simple.txt` con el código convertido.
|
||||
|
||||
### Uso Directo del Convertidor
|
||||
|
||||
```python
|
||||
from simple_lad_converter import SimpleLadConverter
|
||||
|
||||
converter = SimpleLadConverter()
|
||||
converter.parse_file("mi_archivo.EXP")
|
||||
structured_code = converter.save_to_file("salida.txt")
|
||||
```
|
||||
|
||||
### Línea de Comandos
|
||||
|
||||
```bash
|
||||
python lad_to_pseudocode_converter.py archivo_entrada.EXP archivo_salida.txt
|
||||
```
|
||||
|
||||
## Estructura del Código LAD Soportada
|
||||
|
||||
El convertidor puede procesar las siguientes estructuras de TwinCAT:
|
||||
|
||||
### Elementos LAD Reconocidos
|
||||
|
||||
- `_NETWORK` - Inicio de red
|
||||
- `_LD_ASSIGN` - Asignaciones
|
||||
- `_LD_CONTACT` - Contactos (entradas)
|
||||
- `_LD_AND` / `_LD_OR` - Operaciones lógicas
|
||||
- `_FUNCTIONBLOCK` - Bloques de función
|
||||
- `_OPERATOR` - Operadores matemáticos
|
||||
- `_COMMENT` / `_END_COMMENT` - Comentarios
|
||||
- `_OUTPUT` - Variables de salida
|
||||
|
||||
### Operadores Soportados
|
||||
|
||||
- **Aritméticos**: ADD, SUB, MUL, DIV
|
||||
- **Lógicos**: AND, OR
|
||||
- **Comparación**: LT, GT, EQ
|
||||
- **Especiales**: SEL, MOVE
|
||||
|
||||
## Ejemplo de Conversión
|
||||
|
||||
### Código LAD Original
|
||||
```
|
||||
_NETWORK
|
||||
_COMMENT
|
||||
Verificación de presión CO2
|
||||
_END_COMMENT
|
||||
_LD_ASSIGN
|
||||
_LD_CONTACT
|
||||
DI_Air_InletPress_OK
|
||||
_NEGATIV
|
||||
_FUNCTIONBLOCK
|
||||
mAirPressOk
|
||||
_OUTPUT
|
||||
gInLinePressAirOk
|
||||
```
|
||||
|
||||
### Código Estructurado Generado
|
||||
```
|
||||
// Red 5
|
||||
// Verificación de presión CO2
|
||||
IF NOT DI_Air_InletPress_OK THEN
|
||||
gInLinePressAirOk := mAirPressOk();
|
||||
END_IF;
|
||||
```
|
||||
|
||||
## Estructura del Archivo de Salida
|
||||
|
||||
El código generado sigue esta estructura:
|
||||
|
||||
```
|
||||
// Código pseudo estructurado generado desde LAD TwinCAT
|
||||
// Compatible con IEC61131-3
|
||||
PROGRAM Input_Converted
|
||||
|
||||
// Red 1
|
||||
IF condicion1 AND condicion2 THEN
|
||||
variable_salida := funcion_bloque(parametros);
|
||||
END_IF;
|
||||
|
||||
// Red 2
|
||||
variable := operando1 ADD operando2;
|
||||
|
||||
// Red N...
|
||||
|
||||
END_PROGRAM
|
||||
```
|
||||
|
||||
## Resultados de la Conversión
|
||||
|
||||
El convertidor ha procesado exitosamente el archivo `INPUT.EXP` que contiene:
|
||||
|
||||
- **86 redes LAD** en total
|
||||
- **Múltiples tipos de elementos**: contactos, function blocks, operadores
|
||||
- **Preservación de comentarios** originales
|
||||
- **Conversión correcta de lógica condicional** con IF-THEN-END_IF
|
||||
|
||||
### Estadísticas del Ejemplo
|
||||
|
||||
- Archivo de entrada: `.example/INPUT.EXP` (4,611 líneas)
|
||||
- Redes procesadas: 86
|
||||
- Archivo de salida: ~235 líneas de código estructurado
|
||||
- Reducción de complejidad: ~95%
|
||||
|
||||
## Ventajas del Código Convertido
|
||||
|
||||
1. **Legibilidad**: Más fácil de leer que el formato LAD textual
|
||||
2. **Mantenibilidad**: Estructura clara con comentarios preservados
|
||||
3. **Debugging**: Lógica condicional explícita
|
||||
4. **Documentación**: Comentarios de red integrados
|
||||
5. **Portabilidad**: Formato pseudo-código universal
|
||||
|
||||
## Limitaciones Conocidas
|
||||
|
||||
- Algunos parámetros internos pueden aparecer como `_POSITIV`, `_NEGATIV`
|
||||
- Estructuras complejas de LAD pueden requerir revisión manual
|
||||
- El convertidor es específico para el formato de TwinCAT
|
||||
|
||||
## Desarrollo Futuro
|
||||
|
||||
- [ ] Mejorar el parsing de parámetros de function blocks
|
||||
- [ ] Añadir soporte para más tipos de operadores
|
||||
- [ ] Implementar validación de sintaxis
|
||||
- [ ] Crear interfaz gráfica para conversión
|
||||
- [ ] Soporte para otros formatos de PLC
|
||||
|
||||
## Contribuciones
|
||||
|
||||
Este convertidor fue desarrollado para facilitar el análisis y mantenimiento de código LAD en proyectos de automatización industrial. Las contribuciones y mejoras son bienvenidas.
|
||||
|
||||
---
|
||||
|
||||
**Nota**: Este es un proyecto de código abierto para ayudar en la migración y análisis de código LAD de TwinCAT a formatos más estructurados.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,463 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script para generar documentación de adaptación de IOs
|
||||
entre TwinCAT y TIA Portal - Proyecto SIDEL
|
||||
|
||||
Autor: Generado automáticamente
|
||||
Proyecto: E5.007560 - Modifica O&U - SAE235
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import pandas as pd
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
|
||||
# Configurar el path al directorio raíz del proyecto
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
|
||||
# Importar la función de configuración
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
|
||||
def load_tiaportal_adaptations(working_directory, file_path='IO Adapted.md'):
|
||||
"""Carga las adaptaciones de TIA Portal desde el archivo markdown"""
|
||||
full_file_path = os.path.join(working_directory, file_path)
|
||||
print(f"Cargando adaptaciones de TIA Portal desde: {full_file_path}")
|
||||
|
||||
adaptations = {}
|
||||
|
||||
if not os.path.exists(full_file_path):
|
||||
print(f"⚠️ Archivo {full_file_path} no encontrado")
|
||||
return adaptations
|
||||
|
||||
with open(full_file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Patrones mejorados para diferentes tipos de IOs
|
||||
patterns = [
|
||||
# Digitales: E0.0, A0.0
|
||||
r'\|\s*([EA]\d+\.\d+)\s*\|\s*([^|]+?)\s*\|',
|
||||
# Analógicos: PEW100, PAW100
|
||||
r'\|\s*(P[EA]W\d+)\s*\|\s*([^|]+?)\s*\|',
|
||||
# Profibus: EW 1640, AW 1640
|
||||
r'\|\s*([EA]W\s+\d+)\s*\|\s*([^|]+?)\s*\|'
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
matches = re.findall(pattern, content, re.MULTILINE)
|
||||
for io_addr, master_tag in matches:
|
||||
io_addr = io_addr.strip()
|
||||
master_tag = master_tag.strip()
|
||||
if io_addr and master_tag and not master_tag.startswith('-'):
|
||||
adaptations[io_addr] = master_tag
|
||||
print(f" 📍 {io_addr} → {master_tag}")
|
||||
|
||||
print(f"✅ Cargadas {len(adaptations)} adaptaciones de TIA Portal")
|
||||
return adaptations
|
||||
|
||||
def scan_twincat_definitions(working_directory, directory='TwinCat'):
|
||||
"""Escanea archivos TwinCAT para encontrar definiciones de variables AT %"""
|
||||
full_directory = os.path.join(working_directory, directory)
|
||||
print(f"\n🔍 Escaneando definiciones TwinCAT en: {full_directory}")
|
||||
|
||||
definitions = {}
|
||||
|
||||
if not os.path.exists(full_directory):
|
||||
print(f"⚠️ Directorio {full_directory} no encontrado")
|
||||
return definitions
|
||||
|
||||
# Patrones para definiciones AT %
|
||||
definition_patterns = [
|
||||
r'(\w+)\s+AT\s+%([IQ][XWB]\d+(?:\.\d+)?)\s*:\s*(\w+);', # Activas
|
||||
r'(\w+)\s+\(\*\s*AT\s+%([IQ][XWB]\d+(?:\.\d+)?)\s*\*\)\s*:\s*(\w+);', # Comentadas
|
||||
]
|
||||
|
||||
for file_path in Path(full_directory).glob('*.scl'):
|
||||
print(f" 📄 Procesando: {file_path.name}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
for pattern in definition_patterns:
|
||||
matches = re.findall(pattern, content, re.MULTILINE | re.IGNORECASE)
|
||||
for var_name, io_addr, data_type in matches:
|
||||
var_name = var_name.strip()
|
||||
io_addr = io_addr.strip()
|
||||
data_type = data_type.strip()
|
||||
|
||||
definitions[var_name] = {
|
||||
'address': io_addr,
|
||||
'type': data_type,
|
||||
'file': file_path.name,
|
||||
'definition_line': content[:content.find(var_name)].count('\n') + 1
|
||||
}
|
||||
print(f" 🔗 {var_name} AT %{io_addr} : {data_type}")
|
||||
|
||||
print(f"✅ Encontradas {len(definitions)} definiciones TwinCAT")
|
||||
return definitions
|
||||
|
||||
def scan_twincat_usage(working_directory, directory='TwinCat'):
|
||||
"""Escanea archivos TwinCAT para encontrar uso de variables"""
|
||||
full_directory = os.path.join(working_directory, directory)
|
||||
print(f"\n🔍 Escaneando uso de variables TwinCAT en: {full_directory}")
|
||||
|
||||
usage_data = defaultdict(list)
|
||||
|
||||
if not os.path.exists(full_directory):
|
||||
print(f"⚠️ Directorio {full_directory} no encontrado")
|
||||
return usage_data
|
||||
|
||||
for file_path in Path(full_directory).glob('*.scl'):
|
||||
print(f" 📄 Analizando uso en: {file_path.name}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
# Buscar variables que empiecen con DI_, DO_, AI_, AO_
|
||||
var_matches = re.findall(r'\b([DA][IO]_\w+)\b', line)
|
||||
for var_name in var_matches:
|
||||
usage_data[var_name].append({
|
||||
'file': file_path.name,
|
||||
'line': line_num,
|
||||
'context': line.strip()[:100] + ('...' if len(line.strip()) > 100 else '')
|
||||
})
|
||||
|
||||
print(f"✅ Encontrado uso de {len(usage_data)} variables diferentes")
|
||||
return usage_data
|
||||
|
||||
def convert_tia_to_twincat(tia_addr):
|
||||
"""Convierte direcciones TIA Portal a formato TwinCAT"""
|
||||
conversions = []
|
||||
|
||||
# Digitales
|
||||
if re.match(r'^E\d+\.\d+$', tia_addr): # E0.0 → IX0.0
|
||||
twincat_addr = tia_addr.replace('E', 'IX')
|
||||
conversions.append(twincat_addr)
|
||||
elif re.match(r'^A\d+\.\d+$', tia_addr): # A0.0 → QX0.0
|
||||
twincat_addr = tia_addr.replace('A', 'QX')
|
||||
conversions.append(twincat_addr)
|
||||
|
||||
# Analógicos
|
||||
elif re.match(r'^PEW\d+$', tia_addr): # PEW100 → IW100
|
||||
twincat_addr = tia_addr.replace('PEW', 'IW')
|
||||
conversions.append(twincat_addr)
|
||||
elif re.match(r'^PAW\d+$', tia_addr): # PAW100 → QW100
|
||||
twincat_addr = tia_addr.replace('PAW', 'QW')
|
||||
conversions.append(twincat_addr)
|
||||
|
||||
# Profibus
|
||||
elif re.match(r'^EW\s+\d+$', tia_addr): # EW 1234 → IB1234
|
||||
addr_num = re.search(r'\d+', tia_addr).group()
|
||||
conversions.append(f'IB{addr_num}')
|
||||
elif re.match(r'^AW\s+\d+$', tia_addr): # AW 1234 → QB1234
|
||||
addr_num = re.search(r'\d+', tia_addr).group()
|
||||
conversions.append(f'QB{addr_num}')
|
||||
|
||||
return conversions
|
||||
|
||||
def find_variable_by_address(definitions, target_address):
|
||||
"""Busca variable por dirección exacta"""
|
||||
for var_name, info in definitions.items():
|
||||
if info['address'] == target_address:
|
||||
return var_name, info
|
||||
return None, None
|
||||
|
||||
def find_variable_by_name_similarity(definitions, usage_data, master_tag):
|
||||
"""Busca variables por similitud de nombre"""
|
||||
candidates = []
|
||||
|
||||
# Limpiar el master tag para comparación
|
||||
clean_master = re.sub(r'^[DA][IO]_', '', master_tag).lower()
|
||||
|
||||
# Buscar en definiciones
|
||||
for var_name, info in definitions.items():
|
||||
clean_var = re.sub(r'^[DA][IO]_', '', var_name).lower()
|
||||
if clean_master in clean_var or clean_var in clean_master:
|
||||
candidates.append((var_name, info, 'definition'))
|
||||
|
||||
# Buscar en uso
|
||||
for var_name in usage_data.keys():
|
||||
clean_var = re.sub(r'^[DA][IO]_', '', var_name).lower()
|
||||
if clean_master in clean_var or clean_var in clean_master:
|
||||
# Intentar encontrar la definición de esta variable
|
||||
var_info = definitions.get(var_name)
|
||||
if not var_info:
|
||||
var_info = {'address': 'Unknown', 'type': 'Unknown', 'file': 'Not found'}
|
||||
candidates.append((var_name, var_info, 'usage'))
|
||||
|
||||
return candidates
|
||||
|
||||
def analyze_adaptations(tia_adaptations, twincat_definitions, twincat_usage):
|
||||
"""Analiza las correlaciones entre TIA Portal y TwinCAT"""
|
||||
print(f"\n📊 Analizando correlaciones...")
|
||||
|
||||
results = []
|
||||
matches_found = 0
|
||||
|
||||
for tia_addr, master_tag in tia_adaptations.items():
|
||||
result = {
|
||||
'tia_address': tia_addr,
|
||||
'master_tag': master_tag,
|
||||
'twincat_variable': None,
|
||||
'twincat_address': None,
|
||||
'twincat_type': None,
|
||||
'match_type': None,
|
||||
'definition_file': None,
|
||||
'usage_files': [],
|
||||
'usage_count': 0,
|
||||
'confidence': 'Low'
|
||||
}
|
||||
|
||||
# 1. Buscar por conversión directa de dirección
|
||||
twincat_addresses = convert_tia_to_twincat(tia_addr)
|
||||
var_found = False
|
||||
|
||||
for twincat_addr in twincat_addresses:
|
||||
var_name, var_info = find_variable_by_address(twincat_definitions, twincat_addr)
|
||||
if var_name:
|
||||
result.update({
|
||||
'twincat_variable': var_name,
|
||||
'twincat_address': var_info['address'],
|
||||
'twincat_type': var_info['type'],
|
||||
'match_type': 'Address Match',
|
||||
'definition_file': var_info['file'],
|
||||
'confidence': 'High'
|
||||
})
|
||||
var_found = True
|
||||
matches_found += 1
|
||||
break
|
||||
|
||||
# 2. Si no se encontró por dirección, buscar por nombre
|
||||
if not var_found:
|
||||
candidates = find_variable_by_name_similarity(twincat_definitions, twincat_usage, master_tag)
|
||||
if candidates:
|
||||
# Tomar el mejor candidato
|
||||
best_candidate = candidates[0]
|
||||
var_name, var_info, source = best_candidate
|
||||
|
||||
result.update({
|
||||
'twincat_variable': var_name,
|
||||
'twincat_address': var_info.get('address', 'Unknown'),
|
||||
'twincat_type': var_info.get('type', 'Unknown'),
|
||||
'match_type': f'Name Similarity ({source})',
|
||||
'definition_file': var_info.get('file', 'Unknown'),
|
||||
'confidence': 'Medium'
|
||||
})
|
||||
matches_found += 1
|
||||
|
||||
# 3. Buscar información de uso
|
||||
if result['twincat_variable']:
|
||||
var_name = result['twincat_variable']
|
||||
if var_name in twincat_usage:
|
||||
usage_info = twincat_usage[var_name]
|
||||
result['usage_files'] = list(set([u['file'] for u in usage_info]))
|
||||
result['usage_count'] = len(usage_info)
|
||||
|
||||
results.append(result)
|
||||
|
||||
# Log del progreso
|
||||
status = "✅" if result['twincat_variable'] else "❌"
|
||||
print(f" {status} {tia_addr} → {master_tag}")
|
||||
if result['twincat_variable']:
|
||||
print(f" 🔗 {result['twincat_variable']} AT %{result['twincat_address']}")
|
||||
if result['usage_count'] > 0:
|
||||
print(f" 📝 Usado en {result['usage_count']} lugares: {', '.join(result['usage_files'])}")
|
||||
|
||||
print(f"\n🎯 Resumen: {matches_found}/{len(tia_adaptations)} variables correlacionadas ({matches_found/len(tia_adaptations)*100:.1f}%)")
|
||||
|
||||
return results
|
||||
|
||||
def create_results_directory(working_directory):
|
||||
"""Crea el directorio de resultados si no existe"""
|
||||
results_dir = Path(working_directory) / 'resultados'
|
||||
results_dir.mkdir(exist_ok=True)
|
||||
print(f"📁 Directorio de resultados: {results_dir.absolute()}")
|
||||
return results_dir
|
||||
|
||||
def generate_json_output(results, working_directory, output_file='io_adaptation_data.json'):
|
||||
"""Genera archivo JSON con datos estructurados para análisis posterior"""
|
||||
full_output_file = os.path.join(working_directory, 'resultados', output_file)
|
||||
print(f"\n📄 Generando archivo JSON: {full_output_file}")
|
||||
|
||||
json_data = {
|
||||
"metadata": {
|
||||
"generated_at": pd.Timestamp.now().isoformat(),
|
||||
"project": "E5.007560 - Modifica O&U - SAE235",
|
||||
"total_adaptations": len(results),
|
||||
"matched_variables": len([r for r in results if r['twincat_variable']]),
|
||||
"high_confidence": len([r for r in results if r['confidence'] == 'High']),
|
||||
"medium_confidence": len([r for r in results if r['confidence'] == 'Medium'])
|
||||
},
|
||||
"adaptations": []
|
||||
}
|
||||
|
||||
for result in results:
|
||||
adaptation = {
|
||||
"tia_portal": {
|
||||
"address": result['tia_address'],
|
||||
"tag": result['master_tag']
|
||||
},
|
||||
"twincat": {
|
||||
"variable": result['twincat_variable'],
|
||||
"address": result['twincat_address'],
|
||||
"data_type": result['twincat_type'],
|
||||
"definition_file": result['definition_file']
|
||||
},
|
||||
"correlation": {
|
||||
"match_type": result['match_type'],
|
||||
"confidence": result['confidence'],
|
||||
"found": result['twincat_variable'] is not None
|
||||
},
|
||||
"usage": {
|
||||
"usage_count": result['usage_count'],
|
||||
"usage_files": result['usage_files']
|
||||
}
|
||||
}
|
||||
json_data["adaptations"].append(adaptation)
|
||||
|
||||
with open(full_output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"✅ Archivo JSON generado: {full_output_file}")
|
||||
|
||||
def generate_detailed_report(results, working_directory, output_file='IO_Detailed_Analysis_Report.md'):
|
||||
"""Genera un reporte detallado con tabla markdown"""
|
||||
full_output_file = os.path.join(working_directory, 'resultados', output_file)
|
||||
print(f"\n📄 Generando reporte detallado: {full_output_file}")
|
||||
|
||||
with open(full_output_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Reporte Detallado de Análisis de Adaptación IO\n\n")
|
||||
f.write(f"**Fecha de generación:** {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||||
|
||||
# Estadísticas
|
||||
total = len(results)
|
||||
matched = len([r for r in results if r['twincat_variable']])
|
||||
high_conf = len([r for r in results if r['confidence'] == 'High'])
|
||||
medium_conf = len([r for r in results if r['confidence'] == 'Medium'])
|
||||
|
||||
f.write("## 📊 Estadísticas Generales\n\n")
|
||||
f.write(f"- **Total adaptaciones procesadas:** {total}\n")
|
||||
f.write(f"- **Variables encontradas:** {matched} ({matched/total*100:.1f}%)\n")
|
||||
f.write(f"- **Coincidencias de alta confianza:** {high_conf}\n")
|
||||
f.write(f"- **Coincidencias de media confianza:** {medium_conf}\n\n")
|
||||
|
||||
# Tabla de variables correlacionadas exitosamente
|
||||
f.write("## ✅ Variables Correlacionadas Exitosamente\n\n")
|
||||
matched_results = [r for r in results if r['twincat_variable']]
|
||||
|
||||
if matched_results:
|
||||
# Encabezado de la tabla
|
||||
f.write("| TIA Address | TIA Tag | TwinCAT Variable | TwinCAT Address | Tipo | Método | Confianza | Archivo Def. | Uso | Archivos Uso |\n")
|
||||
f.write("|-------------|---------|------------------|-----------------|------|--------|-----------|--------------|-----|---------------|\n")
|
||||
|
||||
# Filas de datos
|
||||
for result in matched_results:
|
||||
usage_files_str = ', '.join(result['usage_files'][:3]) # Limitar a 3 archivos
|
||||
if len(result['usage_files']) > 3:
|
||||
usage_files_str += "..."
|
||||
|
||||
f.write(f"| {result['tia_address']} | "
|
||||
f"`{result['master_tag']}` | "
|
||||
f"`{result['twincat_variable']}` | "
|
||||
f"`%{result['twincat_address']}` | "
|
||||
f"`{result['twincat_type']}` | "
|
||||
f"{result['match_type']} | "
|
||||
f"{result['confidence']} | "
|
||||
f"{result['definition_file']} | "
|
||||
f"{result['usage_count']} | "
|
||||
f"{usage_files_str} |\n")
|
||||
|
||||
f.write("\n")
|
||||
|
||||
# Tabla de variables no encontradas
|
||||
f.write("## ❌ Variables No Encontradas\n\n")
|
||||
unmatched_results = [r for r in results if not r['twincat_variable']]
|
||||
|
||||
if unmatched_results:
|
||||
f.write("| TIA Address | TIA Tag |\n")
|
||||
f.write("|-------------|----------|\n")
|
||||
|
||||
for result in unmatched_results:
|
||||
f.write(f"| {result['tia_address']} | `{result['master_tag']}` |\n")
|
||||
|
||||
f.write(f"\n**Total no encontradas:** {len(unmatched_results)}\n\n")
|
||||
|
||||
# Recomendaciones
|
||||
f.write("## 💡 Recomendaciones\n\n")
|
||||
f.write("1. **Variables de alta confianza** pueden migrarse directamente\n")
|
||||
f.write("2. **Variables de media confianza** requieren verificación manual\n")
|
||||
f.write("3. **Variables no encontradas** requieren mapeo manual o pueden ser obsoletas\n")
|
||||
f.write("4. Variables con uso extensivo son prioritarias para la migración\n\n")
|
||||
|
||||
# Resumen por confianza
|
||||
f.write("## 📈 Distribución por Confianza\n\n")
|
||||
f.write("| Nivel de Confianza | Cantidad | Porcentaje |\n")
|
||||
f.write("|--------------------|----------|------------|\n")
|
||||
f.write(f"| Alta | {high_conf} | {high_conf/total*100:.1f}% |\n")
|
||||
f.write(f"| Media | {medium_conf} | {medium_conf/total*100:.1f}% |\n")
|
||||
f.write(f"| No encontradas | {total-matched} | {(total-matched)/total*100:.1f}% |\n")
|
||||
|
||||
print(f"✅ Reporte detallado generado: {full_output_file}")
|
||||
|
||||
def main():
|
||||
print("🚀 Iniciando análisis detallado de adaptación de IOs TwinCAT ↔ TIA Portal")
|
||||
print("=" * 80)
|
||||
|
||||
# Cargar configuración
|
||||
configs = load_configuration()
|
||||
|
||||
# Verificar que se cargó correctamente
|
||||
if not configs:
|
||||
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
|
||||
working_directory = "./"
|
||||
else:
|
||||
working_directory = configs.get("working_directory", "./")
|
||||
|
||||
# Verificar directorio de trabajo
|
||||
if not os.path.exists(working_directory):
|
||||
print(f"Error: El directorio de trabajo no existe: {working_directory}")
|
||||
return
|
||||
|
||||
print(f"📁 Directorio de trabajo: {working_directory}")
|
||||
|
||||
# Crear directorio de resultados
|
||||
results_dir = create_results_directory(working_directory)
|
||||
|
||||
# Cargar datos
|
||||
tia_adaptations = load_tiaportal_adaptations(working_directory)
|
||||
twincat_definitions = scan_twincat_definitions(working_directory)
|
||||
twincat_usage = scan_twincat_usage(working_directory)
|
||||
|
||||
# Analizar correlaciones
|
||||
results = analyze_adaptations(tia_adaptations, twincat_definitions, twincat_usage)
|
||||
|
||||
# Generar reportes en el directorio de resultados
|
||||
generate_detailed_report(results, working_directory)
|
||||
generate_json_output(results, working_directory)
|
||||
|
||||
# Generar CSV para análisis adicional
|
||||
df = pd.DataFrame(results)
|
||||
csv_file = results_dir / 'io_detailed_analysis.csv'
|
||||
df.to_csv(csv_file, index=False, encoding='utf-8')
|
||||
print(f"✅ Datos exportados a CSV: {csv_file}")
|
||||
|
||||
print(f"\n🎉 Análisis completado exitosamente!")
|
||||
print(f"📁 Archivos generados en: {results_dir.absolute()}")
|
||||
print(f" 📄 {results_dir / 'IO_Detailed_Analysis_Report.md'}")
|
||||
print(f" 📄 {results_dir / 'io_adaptation_data.json'}")
|
||||
print(f" 📄 {results_dir / 'io_detailed_analysis.csv'}")
|
||||
|
||||
return results
|
||||
|
||||
if __name__ == "__main__":
|
||||
results = main()
|
|
@ -1,115 +0,0 @@
|
|||
# Análisis de Adaptación IO - TwinCAT ↔ TIA Portal
|
||||
|
||||
Scripts de análisis automático para correlacionar variables IO entre plataformas TwinCAT y TIA Portal en el proyecto SIDEL E5.007560.
|
||||
|
||||
## 📋 Descripción General
|
||||
|
||||
Este proyecto automatiza el análisis de adaptación de variables de entrada/salida (IO) entre:
|
||||
- **TIA Portal** (Siemens) - Sistema actual
|
||||
- **TwinCAT** (Beckhoff) - Sistema objetivo de migración
|
||||
|
||||
## 🔧 Scripts Incluidos
|
||||
|
||||
### 1. `x1_io_adaptation_script.py` - Análisis de Correlación IO
|
||||
|
||||
**Propósito:** Encuentra y correlaciona variables IO entre ambas plataformas generando reportes detallados.
|
||||
|
||||
**Archivos requeridos:**
|
||||
- `IO Adapted.md` - Tabla de adaptaciones TIA Portal (debe estar en directorio raíz)
|
||||
- `TwinCat/` - Directorio con archivos `.scl` de TwinCAT
|
||||
- `TiaPortal/` - Directorio con archivos `.md` de TIA Portal
|
||||
|
||||
**Archivos generados:**
|
||||
- `resultados/IO_Detailed_Analysis_Report.md` - Reporte con tablas markdown
|
||||
- `resultados/io_adaptation_data.json` - Datos estructurados para análisis
|
||||
- `resultados/io_detailed_analysis.csv` - Datos tabulares
|
||||
|
||||
### 2. `x2_code_snippets_generator.py` - Generador de Snippets de Código
|
||||
|
||||
**Propósito:** Genera snippets de código mostrando el uso real de cada variable en ambas plataformas.
|
||||
|
||||
**Archivos requeridos:**
|
||||
- `resultados/io_adaptation_data.json` - Generado por el script 1
|
||||
- `TwinCat/` - Directorio con archivos `.scl`
|
||||
- `TiaPortal/` - Directorio con archivos `.md`
|
||||
|
||||
**Archivos generados:**
|
||||
- `resultados/IO_Code_Snippets_Report.md` - Snippets de código con contexto
|
||||
- `resultados/IO_Usage_Statistics.md` - Estadísticas de uso
|
||||
|
||||
## 🚀 Uso
|
||||
|
||||
### Paso 1: Ejecutar análisis de correlación
|
||||
```bash
|
||||
python x1_io_adaptation_script.py
|
||||
```
|
||||
|
||||
### Paso 2: Generar snippets de código
|
||||
```bash
|
||||
python x2_code_snippets_generator.py
|
||||
```
|
||||
|
||||
## 📁 Estructura de Directorios Requerida
|
||||
|
||||
```
|
||||
proyecto/
|
||||
├── x1_io_adaptation_script.py
|
||||
├── x2_code_snippets_generator.py
|
||||
├── IO Adapted.md # Tabla de adaptaciones TIA
|
||||
├── TwinCat/ # Archivos .scl TwinCAT
|
||||
│ ├── GLOBAL_VARIABLES_IN_OUT.scl
|
||||
│ ├── INPUT.scl
|
||||
│ └── ... (otros archivos .scl)
|
||||
├── TiaPortal/ # Archivos .md TIA Portal
|
||||
│ ├── Input.md
|
||||
│ ├── Output.md
|
||||
│ └── ... (otros archivos .md)
|
||||
└── resultados/ # Directorio creado automáticamente
|
||||
├── IO_Detailed_Analysis_Report.md
|
||||
├── io_adaptation_data.json
|
||||
├── io_detailed_analysis.csv
|
||||
├── IO_Code_Snippets_Report.md
|
||||
└── IO_Usage_Statistics.md
|
||||
```
|
||||
|
||||
## 🔍 Funcionalidades Principales
|
||||
|
||||
### Script 1 - Análisis de Correlación
|
||||
- ✅ Convierte direcciones TIA Portal a formato TwinCAT
|
||||
- ✅ Busca variables por dirección exacta y similitud de nombres
|
||||
- ✅ Calcula nivel de confianza de correlaciones
|
||||
- ✅ Genera reportes en múltiples formatos (MD, JSON, CSV)
|
||||
|
||||
### Script 2 - Snippets de Código
|
||||
- ✅ Muestra hasta 3 usos por variable por plataforma
|
||||
- ✅ Contexto de 3 líneas (anterior, actual, siguiente)
|
||||
- ✅ Links markdown a archivos fuente
|
||||
- ✅ Estadísticas de uso y archivos más referenciados
|
||||
|
||||
## 📊 Resultados Típicos
|
||||
|
||||
- **Variables procesadas:** ~90-100 adaptaciones IO
|
||||
- **Tasa de correlación:** ~70-80% de variables encontradas
|
||||
- **Confianza alta:** Correlaciones por dirección exacta
|
||||
- **Variable más usada:** Típicamente botones de reset/start/stop
|
||||
|
||||
## 🛠 Dependencias
|
||||
|
||||
```python
|
||||
pandas
|
||||
pathlib (incluida en Python 3.4+)
|
||||
json (incluida en Python estándar)
|
||||
re (incluida en Python estándar)
|
||||
```
|
||||
|
||||
## 📝 Notas Importantes
|
||||
|
||||
1. **Orden de ejecución:** Ejecutar siempre el Script 1 antes que el Script 2
|
||||
2. **Archivos fuente:** Verificar que existan los directorios TwinCat/ y TiaPortal/
|
||||
3. **Codificación:** Los scripts manejan archivos con encoding UTF-8
|
||||
4. **Rendimiento:** El Script 2 puede tardar algunos minutos procesando archivos grandes
|
||||
|
||||
## 👥 Proyecto
|
||||
|
||||
**Proyecto SIDEL:** E5.007560 - Modifica O&U - SAE235
|
||||
**Automatización:** Migración TIA Portal → TwinCAT
|
|
@ -1,315 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script para generar snippets de código de uso de variables IO
|
||||
entre TwinCAT y TIA Portal - Proyecto SIDEL
|
||||
|
||||
Autor: Generado automáticamente
|
||||
Proyecto: E5.007560 - Modifica O&U - SAE235
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
import pandas as pd
|
||||
|
||||
# Configurar el path al directorio raíz del proyecto
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
|
||||
# Importar la función de configuración
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
|
||||
def load_adaptation_data(working_directory, json_file='io_adaptation_data.json'):
|
||||
"""Carga los datos de adaptación desde el archivo JSON"""
|
||||
full_json_file = os.path.join(working_directory, 'resultados', json_file)
|
||||
print(f"📖 Cargando datos de adaptación desde: {full_json_file}")
|
||||
|
||||
if not os.path.exists(full_json_file):
|
||||
print(f"⚠️ Archivo {full_json_file} no encontrado")
|
||||
return None
|
||||
|
||||
with open(full_json_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
print(f"✅ Cargados datos de {data['metadata']['total_adaptations']} adaptaciones")
|
||||
return data
|
||||
|
||||
|
||||
def find_variable_usage_in_file(file_path, variable_name, max_occurrences=3):
|
||||
"""Encuentra el uso de una variable en un archivo específico y retorna el contexto"""
|
||||
if not os.path.exists(file_path):
|
||||
return []
|
||||
|
||||
usages = []
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Buscar todas las líneas que contienen la variable
|
||||
found_lines = []
|
||||
for line_num, line in enumerate(lines):
|
||||
# Buscar la variable como palabra completa (no como parte de otra palabra)
|
||||
if re.search(rf'\b{re.escape(variable_name)}\b', line):
|
||||
found_lines.append((line_num, line.strip()))
|
||||
if len(found_lines) >= max_occurrences:
|
||||
break
|
||||
|
||||
# Para cada ocurrencia, obtener contexto (línea anterior, actual, siguiente)
|
||||
for line_num, line_content in found_lines:
|
||||
context = {
|
||||
'line_number': line_num + 1, # Convertir a 1-indexado
|
||||
'before': lines[line_num - 1].strip() if line_num > 0 else "",
|
||||
'current': line_content,
|
||||
'after': lines[line_num + 1].strip() if line_num < len(lines) - 1 else ""
|
||||
}
|
||||
usages.append(context)
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error leyendo archivo {file_path}: {e}")
|
||||
|
||||
return usages
|
||||
|
||||
|
||||
def find_tia_portal_usage(adaptation, working_directory):
|
||||
"""Busca el uso de variables TIA Portal en archivos markdown"""
|
||||
tia_address = adaptation['tia_portal']['address']
|
||||
tia_tag = adaptation['tia_portal']['tag']
|
||||
|
||||
# Buscar en archivos TIA Portal (principalmente en archivos .md)
|
||||
tia_usages = []
|
||||
|
||||
# Buscar en TiaPortal/ directory
|
||||
tia_portal_dir = Path(working_directory) / 'TiaPortal'
|
||||
if tia_portal_dir.exists():
|
||||
for md_file in tia_portal_dir.glob('*.md'):
|
||||
# Buscar por dirección TIA
|
||||
address_usages = find_variable_usage_in_file(md_file, tia_address, 2)
|
||||
for usage in address_usages:
|
||||
usage['file'] = f"TiaPortal/{md_file.name}"
|
||||
usage['search_term'] = tia_address
|
||||
tia_usages.append(usage)
|
||||
|
||||
# Buscar por tag TIA si es diferente
|
||||
if tia_tag != tia_address:
|
||||
tag_usages = find_variable_usage_in_file(md_file, tia_tag, 1)
|
||||
for usage in tag_usages:
|
||||
usage['file'] = f"TiaPortal/{md_file.name}"
|
||||
usage['search_term'] = tia_tag
|
||||
tia_usages.append(usage)
|
||||
|
||||
# Limitar total de usos TIA
|
||||
if len(tia_usages) >= 3:
|
||||
break
|
||||
|
||||
return tia_usages[:3] # Máximo 3 usos TIA
|
||||
|
||||
|
||||
def find_twincat_usage(adaptation, working_directory):
|
||||
"""Busca el uso de variables TwinCAT en archivos .scl"""
|
||||
if not adaptation['correlation']['found']:
|
||||
return []
|
||||
|
||||
variable_name = adaptation['twincat']['variable']
|
||||
usage_files = adaptation['usage']['usage_files']
|
||||
|
||||
twincat_usages = []
|
||||
|
||||
# Buscar en archivos TwinCAT
|
||||
twincat_dir = Path(working_directory) / 'TwinCat'
|
||||
if twincat_dir.exists():
|
||||
for file_name in usage_files:
|
||||
file_path = twincat_dir / file_name
|
||||
if file_path.exists():
|
||||
usages = find_variable_usage_in_file(file_path, variable_name, 2)
|
||||
for usage in usages:
|
||||
usage['file'] = f"TwinCat/{file_name}"
|
||||
usage['search_term'] = variable_name
|
||||
twincat_usages.append(usage)
|
||||
|
||||
# Limitar por archivo
|
||||
if len(twincat_usages) >= 3:
|
||||
break
|
||||
|
||||
return twincat_usages[:3] # Máximo 3 usos TwinCAT
|
||||
|
||||
|
||||
def generate_code_snippets_report(data, working_directory, output_file='IO_Code_Snippets_Report.md'):
|
||||
"""Genera el reporte con snippets de código"""
|
||||
full_output_file = os.path.join(working_directory, 'resultados', output_file)
|
||||
print(f"\n📄 Generando reporte de snippets: {full_output_file}")
|
||||
|
||||
matched_adaptations = [a for a in data['adaptations'] if a['correlation']['found']]
|
||||
|
||||
with open(full_output_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Reporte de Snippets de Código - Adaptación IO\n\n")
|
||||
f.write(f"**Fecha de generación:** {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
f.write(f"**Proyecto:** {data['metadata']['project']}\n\n")
|
||||
|
||||
f.write("## 📋 Resumen\n\n")
|
||||
f.write(f"- **Variables analizadas:** {len(matched_adaptations)}\n")
|
||||
f.write(f"- **Snippets generados:** Se muestran hasta 3 usos por plataforma\n")
|
||||
f.write(f"- **Formato:** Contexto de 3 líneas (anterior, actual, siguiente)\n\n")
|
||||
|
||||
f.write("---\n\n")
|
||||
|
||||
# Procesar cada adaptación
|
||||
for i, adaptation in enumerate(matched_adaptations, 1):
|
||||
tia_address = adaptation['tia_portal']['address']
|
||||
tia_tag = adaptation['tia_portal']['tag']
|
||||
twincat_var = adaptation['twincat']['variable']
|
||||
twincat_addr = adaptation['twincat']['address']
|
||||
|
||||
print(f" 📝 Procesando {i}/{len(matched_adaptations)}: {tia_address} → {twincat_var}")
|
||||
|
||||
f.write(f"## {i}. {tia_address} → {twincat_var}\n\n")
|
||||
f.write(f"**TIA Portal:** `{tia_tag}` (`{tia_address}`)\n")
|
||||
f.write(f"**TwinCAT:** `{twincat_var}` (`%{twincat_addr}`)\n")
|
||||
f.write(f"**Tipo:** `{adaptation['twincat']['data_type']}`\n\n")
|
||||
|
||||
# Buscar usos en TIA Portal
|
||||
f.write("### 🔵 Uso en TIA Portal\n\n")
|
||||
tia_usages = find_tia_portal_usage(adaptation, working_directory)
|
||||
|
||||
if tia_usages:
|
||||
for j, usage in enumerate(tia_usages):
|
||||
f.write(f"**Uso {j+1}:** [{usage['file']}]({usage['file']}) - Línea {usage['line_number']}\n\n")
|
||||
f.write("```scl\n")
|
||||
if usage['before']:
|
||||
f.write(f"{usage['before']}\n")
|
||||
f.write(f">>> {usage['current']} // ← {usage['search_term']}\n")
|
||||
if usage['after']:
|
||||
f.write(f"{usage['after']}\n")
|
||||
f.write("```\n\n")
|
||||
else:
|
||||
f.write("*No se encontraron usos específicos en archivos TIA Portal.*\n\n")
|
||||
|
||||
# Buscar usos en TwinCAT
|
||||
f.write("### 🟢 Uso en TwinCAT\n\n")
|
||||
twincat_usages = find_twincat_usage(adaptation, working_directory)
|
||||
|
||||
if twincat_usages:
|
||||
for j, usage in enumerate(twincat_usages):
|
||||
f.write(f"**Uso {j+1}:** [{usage['file']}]({usage['file']}) - Línea {usage['line_number']}\n\n")
|
||||
f.write("```scl\n")
|
||||
if usage['before']:
|
||||
f.write(f"{usage['before']}\n")
|
||||
f.write(f">>> {usage['current']} // ← {usage['search_term']}\n")
|
||||
if usage['after']:
|
||||
f.write(f"{usage['after']}\n")
|
||||
f.write("```\n\n")
|
||||
else:
|
||||
f.write("*Variable definida pero no se encontraron usos específicos.*\n\n")
|
||||
|
||||
f.write("---\n\n")
|
||||
|
||||
print(f"✅ Reporte de snippets generado: {full_output_file}")
|
||||
|
||||
|
||||
def generate_summary_statistics(data, working_directory, output_file='IO_Usage_Statistics.md'):
|
||||
"""Genera estadísticas de uso de las variables"""
|
||||
full_output_file = os.path.join(working_directory, 'resultados', output_file)
|
||||
print(f"\n📊 Generando estadísticas de uso: {full_output_file}")
|
||||
|
||||
matched_adaptations = [a for a in data['adaptations'] if a['correlation']['found']]
|
||||
|
||||
# Calcular estadísticas
|
||||
total_usage = sum(a['usage']['usage_count'] for a in matched_adaptations)
|
||||
variables_with_usage = len([a for a in matched_adaptations if a['usage']['usage_count'] > 0])
|
||||
|
||||
# Variables más usadas
|
||||
most_used = sorted(matched_adaptations, key=lambda x: x['usage']['usage_count'], reverse=True)[:10]
|
||||
|
||||
# Archivos más referenciados
|
||||
file_usage = {}
|
||||
for adaptation in matched_adaptations:
|
||||
for file_name in adaptation['usage']['usage_files']:
|
||||
file_usage[file_name] = file_usage.get(file_name, 0) + 1
|
||||
|
||||
top_files = sorted(file_usage.items(), key=lambda x: x[1], reverse=True)[:10]
|
||||
|
||||
with open(full_output_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Estadísticas de Uso de Variables IO\n\n")
|
||||
f.write(f"**Fecha de generación:** {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||||
|
||||
f.write("## 📊 Resumen General\n\n")
|
||||
f.write(f"- **Variables correlacionadas:** {len(matched_adaptations)}\n")
|
||||
f.write(f"- **Variables con uso documentado:** {variables_with_usage}\n")
|
||||
f.write(f"- **Total de usos encontrados:** {total_usage}\n")
|
||||
f.write(f"- **Promedio de usos por variable:** {total_usage/len(matched_adaptations):.1f}\n\n")
|
||||
|
||||
f.write("## 🔥 Top 10 Variables Más Usadas\n\n")
|
||||
f.write("| Ranking | TIA Address | TwinCAT Variable | Usos | Archivos |\n")
|
||||
f.write("|---------|-------------|------------------|------|----------|\n")
|
||||
|
||||
for i, adaptation in enumerate(most_used, 1):
|
||||
files_str = ', '.join(adaptation['usage']['usage_files'][:3])
|
||||
if len(adaptation['usage']['usage_files']) > 3:
|
||||
files_str += '...'
|
||||
|
||||
f.write(f"| {i} | {adaptation['tia_portal']['address']} | "
|
||||
f"`{adaptation['twincat']['variable']}` | "
|
||||
f"{adaptation['usage']['usage_count']} | {files_str} |\n")
|
||||
|
||||
f.write("\n## 📁 Top 10 Archivos Más Referenciados\n\n")
|
||||
f.write("| Ranking | Archivo | Variables Usadas |\n")
|
||||
f.write("|---------|---------|------------------|\n")
|
||||
|
||||
for i, (file_name, count) in enumerate(top_files, 1):
|
||||
f.write(f"| {i} | `{file_name}` | {count} |\n")
|
||||
|
||||
print(f"✅ Estadísticas de uso generadas: {full_output_file}")
|
||||
|
||||
|
||||
def main():
|
||||
print("🚀 Iniciando generación de snippets de código para adaptación IO")
|
||||
print("=" * 70)
|
||||
|
||||
# Cargar configuración
|
||||
configs = load_configuration()
|
||||
|
||||
# Verificar que se cargó correctamente
|
||||
if not configs:
|
||||
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
|
||||
working_directory = "./"
|
||||
else:
|
||||
working_directory = configs.get("working_directory", "./")
|
||||
|
||||
# Verificar directorio de trabajo
|
||||
if not os.path.exists(working_directory):
|
||||
print(f"Error: El directorio de trabajo no existe: {working_directory}")
|
||||
return
|
||||
|
||||
print(f"📁 Directorio de trabajo: {working_directory}")
|
||||
|
||||
# Crear directorio de resultados si no existe
|
||||
results_dir = Path(working_directory) / 'resultados'
|
||||
results_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Cargar datos de adaptación
|
||||
data = load_adaptation_data(working_directory)
|
||||
if not data:
|
||||
print("❌ No se pudieron cargar los datos de adaptación")
|
||||
return
|
||||
|
||||
# Generar reporte de snippets
|
||||
generate_code_snippets_report(data, working_directory)
|
||||
|
||||
# Generar estadísticas de uso
|
||||
generate_summary_statistics(data, working_directory)
|
||||
|
||||
print(f"\n🎉 Generación completada exitosamente!")
|
||||
print(f"📁 Archivos generados en: {results_dir.absolute()}")
|
||||
print(f" 📄 {results_dir / 'IO_Code_Snippets_Report.md'}")
|
||||
print(f" 📄 {results_dir / 'IO_Usage_Statistics.md'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,11 +1,25 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "XML Parser to SCL",
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "C:/Trabajo/SIDEL/13 - E5.007560 - Modifica O&U - SAE235/Reporte/ExportTia"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "python",
|
||||
"files.associations": {
|
||||
"*.xml": "xml",
|
||||
"*.scl": "structured-text"
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"ms-python.python",
|
||||
"ms-python.flake8",
|
||||
"ms-python.black-formatter"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -15,5 +15,5 @@
|
|||
"xref_source_subdir": "source"
|
||||
},
|
||||
"level3": {},
|
||||
"working_directory": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia"
|
||||
"working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source"
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"path": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
|
||||
"path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
|
||||
"history": [
|
||||
"C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
|
||||
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
|
||||
"C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
|
||||
"D:\\Trabajo\\VM\\22 - 93841 - Sidel - Tilting\\Reporte\\TiaExports",
|
||||
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML",
|
||||
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
|
||||
|
|
26667
data/log.txt
26667
data/log.txt
File diff suppressed because it is too large
Load Diff
|
@ -978,3 +978,45 @@ class CSharpLauncherManager:
|
|||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning favorites for project {project_id}: {e}")
|
||||
|
||||
def find_solution_file(self, project_id: str) -> Optional[str]:
|
||||
"""
|
||||
Buscar archivo .sln en el directorio del proyecto.
|
||||
Prioridades:
|
||||
1. Si hay un solo .sln, retornarlo
|
||||
2. Si hay múltiples .sln, buscar uno que coincida con el nombre del directorio
|
||||
3. Si no hay coincidencias exactas, retornar el primero alfabéticamente
|
||||
4. Si no hay .sln, retornar None
|
||||
"""
|
||||
try:
|
||||
project = self.get_csharp_project(project_id)
|
||||
if not project:
|
||||
return None
|
||||
|
||||
project_dir = project["directory"]
|
||||
if not os.path.isdir(project_dir):
|
||||
return None
|
||||
|
||||
# Buscar archivos .sln en el directorio raíz del proyecto
|
||||
sln_pattern = os.path.join(project_dir, "*.sln")
|
||||
sln_files = glob.glob(sln_pattern)
|
||||
|
||||
if not sln_files:
|
||||
return None
|
||||
|
||||
if len(sln_files) == 1:
|
||||
return sln_files[0]
|
||||
|
||||
# Si hay múltiples archivos, buscar uno que coincida con el nombre del directorio
|
||||
project_name = os.path.basename(project_dir.rstrip(os.sep))
|
||||
for sln_file in sln_files:
|
||||
sln_name = os.path.splitext(os.path.basename(sln_file))[0]
|
||||
if sln_name.lower() == project_name.lower():
|
||||
return sln_file
|
||||
|
||||
# Si no hay coincidencias exactas, retornar el primero alfabéticamente
|
||||
return sorted(sln_files)[0]
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error finding solution file for project {project_id}: {e}")
|
||||
return None
|
|
@ -3,13 +3,17 @@
|
|||
Factory class for creating language detection services
|
||||
"""
|
||||
from typing import Optional, Set
|
||||
from .base import LanguageDetectionService
|
||||
from .langid_service import LangIdService
|
||||
|
||||
|
||||
class LanguageFactory:
|
||||
"""Factory class for creating language detection service instances"""
|
||||
|
||||
@staticmethod
|
||||
def create_service(service_type: str, allowed_languages: Optional[Set[str]] = None, **kwargs) -> Optional['LanguageDetectionService']:
|
||||
def create_service(
|
||||
service_type: str, allowed_languages: Optional[Set[str]] = None, **kwargs
|
||||
) -> Optional[LanguageDetectionService]:
|
||||
"""
|
||||
Create an instance of the specified language detection service
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# services/llm/claude_service.py
|
||||
"""
|
||||
Claude (Anthropic) service implementation
|
||||
"""
|
||||
import anthropic
|
||||
from typing import Dict, List
|
||||
import json
|
||||
from .base import LLMService
|
||||
from config.api_keys import APIKeyManager
|
||||
from utils.logger import setup_logger
|
||||
|
||||
|
||||
class ClaudeService(LLMService):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "claude-3-7-sonnet-20250219", # Debemos usar el modelo claude-3-7-sonnet-20250219
|
||||
temperature: float = 0.3,
|
||||
max_tokens: int = 16000,
|
||||
):
|
||||
api_key = APIKeyManager.get_claude_key()
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"Claude API key not found. Please set the CLAUDE_API_KEY environment variable."
|
||||
)
|
||||
|
||||
self.client = anthropic.Anthropic(api_key=api_key)
|
||||
self.model = model
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
self.logger = setup_logger("claude")
|
||||
|
||||
def generate_text(self, prompt: str) -> str:
|
||||
self.logger.info(f"--- PROMPT ---\n{prompt}")
|
||||
try:
|
||||
message = self.client.messages.create(
|
||||
model=self.model,
|
||||
max_tokens=self.max_tokens,
|
||||
temperature=self.temperature,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt,
|
||||
}
|
||||
],
|
||||
)
|
||||
response_content = message.content[0].text
|
||||
self.logger.info(f"--- RESPONSE ---\n{response_content}")
|
||||
return response_content
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in Claude API call: {e}")
|
||||
print(f"Error in Claude API call: {e}")
|
||||
return None
|
||||
|
||||
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
|
||||
# Claude's API doesn't have a dedicated similarity or JSON mode endpoint as straightforward as others.
|
||||
# We will instruct it to return JSON.
|
||||
system_prompt = (
|
||||
"You are an expert in semantic analysis. Evaluate the semantic similarity between the pairs of texts provided. "
|
||||
"Return your response ONLY as a JSON object containing a single key 'similarity_scores' with a list of floats from 0.0 to 1.0. "
|
||||
"Do not include any other text, explanation, or markdown formatting. The output must be a valid JSON."
|
||||
)
|
||||
|
||||
request_payload = json.dumps(texts_pairs)
|
||||
|
||||
try:
|
||||
message = self.client.messages.create(
|
||||
model=self.model,
|
||||
max_tokens=self.max_tokens,
|
||||
temperature=self.temperature,
|
||||
system=system_prompt,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": request_payload,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
response_content = message.content[0].text
|
||||
|
||||
try:
|
||||
# Find the JSON part of the response
|
||||
json_start = response_content.find("{")
|
||||
json_end = response_content.rfind("}") + 1
|
||||
if json_start == -1 or json_end == 0:
|
||||
raise ValueError("No JSON object found in the response.")
|
||||
|
||||
json_str = response_content[json_start:json_end]
|
||||
scores_data = json.loads(json_str)
|
||||
|
||||
if isinstance(scores_data, dict) and "similarity_scores" in scores_data:
|
||||
return scores_data["similarity_scores"]
|
||||
else:
|
||||
raise ValueError("Unexpected JSON format from Claude.")
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
print(f"Error decoding Claude JSON response: {e}")
|
||||
raise ValueError(
|
||||
"Could not decode or parse similarity scores from Claude response."
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in Claude similarity calculation: {e}")
|
||||
return None
|
|
@ -0,0 +1,83 @@
|
|||
# services/llm/gemini_service.py
|
||||
"""
|
||||
Gemini (Google) service implementation
|
||||
"""
|
||||
import google.generativeai as genai
|
||||
from typing import Dict, List
|
||||
import json
|
||||
from .base import LLMService
|
||||
from config.api_keys import APIKeyManager
|
||||
from utils.logger import setup_logger
|
||||
|
||||
|
||||
class GeminiService(LLMService):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "gemini-1.5-flash",
|
||||
temperature: float = 0.3,
|
||||
max_tokens: int = 16000,
|
||||
):
|
||||
api_key = APIKeyManager.get_gemini_key()
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"Gemini API key not found. Please set the GEMINI_API_KEY environment variable."
|
||||
)
|
||||
|
||||
genai.configure(api_key=api_key)
|
||||
self.model = genai.GenerativeModel(model)
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
self.logger = setup_logger("gemini")
|
||||
|
||||
def generate_text(self, prompt: str) -> str:
|
||||
self.logger.info(f"--- PROMPT ---\n{prompt}")
|
||||
try:
|
||||
generation_config = genai.types.GenerationConfig(
|
||||
max_output_tokens=self.max_tokens, temperature=self.temperature
|
||||
)
|
||||
response = self.model.generate_content(
|
||||
prompt, generation_config=generation_config
|
||||
)
|
||||
response_content = response.text
|
||||
self.logger.info(f"--- RESPONSE ---\n{response_content}")
|
||||
return response_content
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in Gemini API call: {e}")
|
||||
print(f"Error in Gemini API call: {e}")
|
||||
return None
|
||||
|
||||
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
|
||||
system_prompt = (
|
||||
"You are an expert in semantic analysis. Evaluate the semantic similarity between the pairs of texts provided. "
|
||||
"Return your response ONLY as a JSON object containing a single key 'similarity_scores' with a list of floats from 0.0 to 1.0. "
|
||||
"Do not include any other text, explanation, or markdown formatting. The output must be a valid JSON."
|
||||
)
|
||||
|
||||
request_payload = json.dumps(texts_pairs)
|
||||
full_prompt = f"{system_prompt}\n\n{request_payload}"
|
||||
|
||||
try:
|
||||
generation_config = genai.types.GenerationConfig(
|
||||
max_output_tokens=self.max_tokens,
|
||||
temperature=self.temperature,
|
||||
response_mime_type="application/json",
|
||||
)
|
||||
response = self.model.generate_content(
|
||||
full_prompt, generation_config=generation_config
|
||||
)
|
||||
response_content = response.text
|
||||
|
||||
try:
|
||||
scores_data = json.loads(response_content)
|
||||
if isinstance(scores_data, dict) and "similarity_scores" in scores_data:
|
||||
return scores_data["similarity_scores"]
|
||||
else:
|
||||
raise ValueError("Unexpected JSON format from Gemini.")
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
print(f"Error decoding Gemini JSON response: {e}")
|
||||
raise ValueError(
|
||||
"Could not decode or parse similarity scores from Gemini response."
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error in Gemini similarity calculation: {e}")
|
||||
return None
|
|
@ -1,63 +1,106 @@
|
|||
# services/llm/grok_service.py
|
||||
"""
|
||||
Grok service implementation
|
||||
Grok (xAI) service implementation
|
||||
"""
|
||||
import httpx
|
||||
from typing import Dict, List, Optional
|
||||
import json
|
||||
from .base import LLMService
|
||||
from config.api_keys import APIKeyManager
|
||||
from utils.logger import setup_logger
|
||||
|
||||
|
||||
class GrokService(LLMService):
|
||||
def __init__(self, model: str = "grok-1", temperature: float = 0.3):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "grok-3-mini-fast",
|
||||
temperature: float = 0.3,
|
||||
max_tokens: int = 16000,
|
||||
): # Debemos usar el modelo grok-3-mini-fast
|
||||
api_key = APIKeyManager.get_grok_key()
|
||||
if not api_key:
|
||||
raise ValueError("Grok API key not found. Please set up your API keys.")
|
||||
raise ValueError(
|
||||
"Grok API key not found. Please set the GROK_API_KEY environment variable."
|
||||
)
|
||||
|
||||
self.api_key = api_key
|
||||
self.model = model
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
self.base_url = "https://api.x.ai/v1"
|
||||
self.client = httpx.Client(
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
)
|
||||
self.logger = setup_logger("grok_xai")
|
||||
|
||||
def _send_request(self, payload: Dict) -> Optional[Dict]:
|
||||
"""Sends a request to the Grok API."""
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.base_url}/chat/completions", json=payload, timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
self.logger.error(
|
||||
f"Error in Grok API call: {e.response.status_code} - {e.response.text}"
|
||||
)
|
||||
print(
|
||||
f"Error in Grok API call: {e.response.status_code} - {e.response.text}"
|
||||
)
|
||||
return None
|
||||
except Exception as e:
|
||||
self.logger.error(f"An unexpected error occurred: {e}")
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
return None
|
||||
|
||||
def generate_text(self, prompt: str) -> str:
|
||||
"""
|
||||
Generate text using the Grok API
|
||||
TODO: Update this method when Grok API is available
|
||||
"""
|
||||
try:
|
||||
# Placeholder for Grok API implementation
|
||||
# Update this when the API is released
|
||||
raise NotImplementedError("Grok API is not implemented yet")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in Grok API call: {e}")
|
||||
return None
|
||||
self.logger.info(f"--- PROMPT ---\n{prompt}")
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": self.temperature,
|
||||
"max_tokens": self.max_tokens,
|
||||
}
|
||||
response_data = self._send_request(payload)
|
||||
if response_data and response_data.get("choices"):
|
||||
response_content = response_data["choices"][0]["message"]["content"]
|
||||
self.logger.info(f"--- RESPONSE ---\n{response_content}")
|
||||
return response_content
|
||||
return "Failed to get a response from Grok."
|
||||
|
||||
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
|
||||
"""
|
||||
Calculate similarity scores using the Grok API
|
||||
TODO: Update this method when Grok API is available
|
||||
"""
|
||||
try:
|
||||
system_prompt = (
|
||||
"Evaluate the semantic similarity between the following table of pairs of texts "
|
||||
"in json format on a scale from 0 to 1. Return the similarity scores for every "
|
||||
"row in JSON format as a list of numbers, without any additional text or formatting."
|
||||
"You are an expert in semantic analysis. Evaluate the semantic similarity between the pairs of texts provided. "
|
||||
"Return your response ONLY as a JSON object containing a single key 'similarity_scores' with a list of floats from 0.0 to 1.0. "
|
||||
"Do not include any other text, explanation, or markdown formatting. The output must be a valid JSON."
|
||||
)
|
||||
|
||||
request_payload = json.dumps(texts_pairs)
|
||||
|
||||
# Placeholder for Grok API implementation
|
||||
# Update this when the API is released
|
||||
raise NotImplementedError("Grok API is not implemented yet")
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": request_payload},
|
||||
],
|
||||
"temperature": self.temperature,
|
||||
"max_tokens": self.max_tokens,
|
||||
"response_format": {"type": "json_object"},
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in Grok similarity calculation: {e}")
|
||||
response_data = self._send_request(payload)
|
||||
if response_data and response_data.get("choices"):
|
||||
response_content = response_data["choices"][0]["message"]["content"]
|
||||
try:
|
||||
scores_data = json.loads(response_content)
|
||||
if isinstance(scores_data, dict) and "similarity_scores" in scores_data:
|
||||
return scores_data["similarity_scores"]
|
||||
else:
|
||||
raise ValueError("Unexpected JSON format from Grok.")
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
print(f"Error decoding Grok JSON response: {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
# Update config/api_keys.py to include Grok
|
||||
@classmethod
|
||||
def get_grok_key(cls) -> Optional[str]:
|
||||
"""Get Grok API key from environment or stored configuration"""
|
||||
return (
|
||||
os.getenv('GROK_API_KEY') or
|
||||
cls._get_stored_key('grok')
|
||||
)
|
|
@ -0,0 +1,84 @@
|
|||
# services/llm/groq_service.py
|
||||
"""
|
||||
Groq service implementation
|
||||
"""
|
||||
from groq import Groq
|
||||
from typing import Dict, List
|
||||
import json
|
||||
from .base import LLMService
|
||||
from config.api_keys import APIKeyManager
|
||||
from utils.logger import setup_logger
|
||||
|
||||
|
||||
class GroqService(LLMService):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "llama3-8b-8192",
|
||||
temperature: float = 0.3,
|
||||
max_tokens: int = 8000,
|
||||
):
|
||||
api_key = APIKeyManager.get_groq_key()
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"Groq API key not found. Please set the GROQ_API_KEY environment variable."
|
||||
)
|
||||
|
||||
self.client = Groq(api_key=api_key)
|
||||
self.model = model
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
self.logger = setup_logger("groq")
|
||||
|
||||
def generate_text(self, prompt: str) -> str:
|
||||
self.logger.info(f"--- PROMPT ---\n{prompt}")
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=self.temperature,
|
||||
max_tokens=self.max_tokens,
|
||||
)
|
||||
response_content = response.choices[0].message.content
|
||||
self.logger.info(f"--- RESPONSE ---\n{response_content}")
|
||||
return response_content
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in Groq API call: {e}")
|
||||
print(f"Error in Groq API call: {e}")
|
||||
return None
|
||||
|
||||
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
|
||||
system_prompt = (
|
||||
"Evaluate the semantic similarity between the following table of pairs of texts in json format on a scale from 0 to 1. "
|
||||
"Return the similarity scores for every row in JSON format as a list of numbers, without any additional text or formatting."
|
||||
)
|
||||
|
||||
request_payload = json.dumps(texts_pairs)
|
||||
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": request_payload},
|
||||
],
|
||||
temperature=self.temperature,
|
||||
max_tokens=self.max_tokens,
|
||||
response_format={"type": "json_object"},
|
||||
)
|
||||
|
||||
response_content = response.choices[0].message.content
|
||||
|
||||
try:
|
||||
scores = json.loads(response_content)
|
||||
if isinstance(scores, dict) and "similarity_scores" in scores:
|
||||
return scores["similarity_scores"]
|
||||
elif isinstance(scores, list):
|
||||
return scores
|
||||
else:
|
||||
raise ValueError("Unexpected response format")
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError("Could not decode response as JSON")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in Groq similarity calculation: {e}")
|
||||
return None
|
File diff suppressed because it is too large
Load Diff
|
@ -5,24 +5,32 @@ Factory class for creating LLM services
|
|||
from typing import Optional
|
||||
from .openai_service import OpenAIService
|
||||
from .ollama_service import OllamaService
|
||||
from .groq_service import GroqService
|
||||
from .claude_service import ClaudeService
|
||||
from .grok_service import GrokService
|
||||
from .gemini_service import GeminiService
|
||||
from .base import LLMService
|
||||
|
||||
|
||||
class LLMFactory:
|
||||
"""Factory class for creating LLM service instances"""
|
||||
|
||||
@staticmethod
|
||||
def create_service(service_type: str, **kwargs) -> Optional['LLMService']:
|
||||
def create_service(service_type: str, **kwargs) -> Optional[LLMService]:
|
||||
"""
|
||||
Create an instance of the specified LLM service
|
||||
|
||||
Args:
|
||||
service_type: Type of LLM service ("openai", "ollama", "grok")
|
||||
service_type: Type of LLM service ("openai", "ollama", "groq", "claude", "grok")
|
||||
**kwargs: Additional arguments for service initialization
|
||||
"""
|
||||
services = {
|
||||
"openai": OpenAIService,
|
||||
"ollama": OllamaService,
|
||||
"grok": GrokService
|
||||
"groq": GroqService,
|
||||
"claude": ClaudeService,
|
||||
"grok": GrokService,
|
||||
"gemini": GeminiService,
|
||||
}
|
||||
|
||||
service_class = services.get(service_type.lower())
|
||||
|
|
|
@ -6,19 +6,29 @@ import ollama
|
|||
import json
|
||||
from typing import Dict, List
|
||||
from .base import LLMService
|
||||
from utils.logger import setup_logger
|
||||
|
||||
|
||||
class OllamaService(LLMService):
|
||||
def __init__(self, model: str = "llama3.1"):
|
||||
def __init__(self, model: str = "qwen3:latest", max_tokens: int = 4000):
|
||||
self.model = model
|
||||
# Explicitly set the host to avoid potential DNS/proxy issues with 'localhost'
|
||||
self.client = ollama.Client(host="127.0.0.1:11434")
|
||||
self.max_tokens = max_tokens
|
||||
self.logger = setup_logger("ollama")
|
||||
|
||||
def generate_text(self, prompt: str) -> str:
|
||||
self.logger.info(f"--- PROMPT ---\n{prompt}")
|
||||
try:
|
||||
response = ollama.generate(
|
||||
model=self.model,
|
||||
prompt=prompt
|
||||
options = {"num_predict": self.max_tokens}
|
||||
response = self.client.generate(
|
||||
model=self.model, prompt=prompt, options=options
|
||||
)
|
||||
return response["response"]
|
||||
response_content = response["response"]
|
||||
self.logger.info(f"--- RESPONSE ---\n{response_content}")
|
||||
return response_content
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in Ollama API call: {e}")
|
||||
print(f"Error in Ollama API call: {e}")
|
||||
return None
|
||||
|
||||
|
@ -32,9 +42,9 @@ class OllamaService(LLMService):
|
|||
prompt = f"{system_prompt}\n\n{request_payload}"
|
||||
|
||||
try:
|
||||
response = ollama.generate(
|
||||
model=self.model,
|
||||
prompt=prompt
|
||||
options = {"num_predict": self.max_tokens}
|
||||
response = self.client.generate(
|
||||
model=self.model, prompt=prompt, options=options
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
|
@ -6,28 +6,43 @@ from openai import OpenAI
|
|||
from typing import Dict, List
|
||||
import json
|
||||
from .base import LLMService
|
||||
from openai_api_key import openai_api_key
|
||||
from config.api_keys import APIKeyManager
|
||||
from utils.logger import setup_logger
|
||||
|
||||
|
||||
class OpenAIService(LLMService):
|
||||
def __init__(self, model: str = "gpt-4o-mini", temperature: float = 0.3):
|
||||
api_key = openai_api_key()
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 0.3,
|
||||
max_tokens: int = 16000,
|
||||
):
|
||||
api_key = APIKeyManager.get_openai_key()
|
||||
if not api_key:
|
||||
raise ValueError("OpenAI API key not found. Please set up your API keys.")
|
||||
raise ValueError(
|
||||
"OpenAI API key not found. Please set the OPENAI_API_KEY environment variable."
|
||||
)
|
||||
|
||||
self.client = OpenAI(api_key=api_key)
|
||||
self.model = model
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
self.logger = setup_logger("openai")
|
||||
|
||||
def generate_text(self, prompt: str) -> str:
|
||||
self.logger.info(f"--- PROMPT ---\n{prompt}")
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=self.temperature,
|
||||
max_tokens=1500
|
||||
max_tokens=self.max_tokens,
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
response_content = response.choices[0].message.content
|
||||
self.logger.info(f"--- RESPONSE ---\n{response_content}")
|
||||
return response_content
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in OpenAI API call: {e}")
|
||||
print(f"Error in OpenAI API call: {e}")
|
||||
return None
|
||||
|
||||
|
@ -44,10 +59,10 @@ class OpenAIService(LLMService):
|
|||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": request_payload}
|
||||
{"role": "user", "content": request_payload},
|
||||
],
|
||||
temperature=self.temperature,
|
||||
max_tokens=1500
|
||||
max_tokens=self.max_tokens,
|
||||
)
|
||||
|
||||
response_content = response.choices[0].message.content
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import os
|
||||
from dotenv import load_dotenv
|
||||
from llm_factory import LLMFactory
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to test the LLM services."""
|
||||
print("Testing LLM Services...")
|
||||
|
||||
# --- Test OpenAI ---
|
||||
try:
|
||||
print("\n--- Testing OpenAI ---")
|
||||
openai_service = LLMFactory.create_service("openai")
|
||||
if openai_service:
|
||||
prompt_openai = "Explain the importance of structured JSON output from LLMs in one sentence."
|
||||
response_openai = openai_service.generate_text(prompt_openai)
|
||||
print(f"OpenAI Prompt: {prompt_openai}")
|
||||
print(f"OpenAI Response: {response_openai}")
|
||||
else:
|
||||
print("Failed to create OpenAI service.")
|
||||
except Exception as e:
|
||||
print(f"Error testing OpenAI: {e}")
|
||||
|
||||
# --- Test Groq ---
|
||||
try:
|
||||
print("\n--- Testing Groq ---")
|
||||
groq_service = LLMFactory.create_service("groq")
|
||||
if groq_service:
|
||||
prompt_groq = (
|
||||
"Explain the concept of 'inference speed' for LLMs in one sentence."
|
||||
)
|
||||
response_groq = groq_service.generate_text(prompt_groq)
|
||||
print(f"Groq Prompt: {prompt_groq}")
|
||||
print(f"Groq Response: {response_groq}")
|
||||
else:
|
||||
print("Failed to create Groq service.")
|
||||
except Exception as e:
|
||||
print(f"Error testing Groq: {e}")
|
||||
|
||||
# --- Test Claude ---
|
||||
try:
|
||||
print("\n--- Testing Claude ---")
|
||||
claude_service = LLMFactory.create_service("claude")
|
||||
if claude_service:
|
||||
prompt_claude = (
|
||||
"What is Anthropic's Constitutional AI concept in one sentence?"
|
||||
)
|
||||
response_claude = claude_service.generate_text(prompt_claude)
|
||||
print(f"Claude Prompt: {prompt_claude}")
|
||||
print(f"Claude Response: {response_claude}")
|
||||
else:
|
||||
print("Failed to create Claude service.")
|
||||
except Exception as e:
|
||||
print(f"Error testing Claude: {e}")
|
||||
|
||||
# --- Test Grok (xAI) ---
|
||||
try:
|
||||
print("\n--- Testing Grok (xAI) ---")
|
||||
grok_service = LLMFactory.create_service("grok")
|
||||
if grok_service:
|
||||
prompt_grok = "What is the mission of xAI in one sentence?"
|
||||
response_grok = grok_service.generate_text(prompt_grok)
|
||||
print(f"Grok Prompt: {prompt_grok}")
|
||||
print(f"Grok Response: {response_grok}")
|
||||
else:
|
||||
print("Failed to create Grok service.")
|
||||
except Exception as e:
|
||||
print(f"Error testing Grok (xAI): {e}")
|
||||
|
||||
# --- Test Ollama ---
|
||||
try:
|
||||
print("\n--- Testing Ollama ---")
|
||||
# Make sure you have an Ollama model running, e.g., `ollama run llama3.1`
|
||||
ollama_service = LLMFactory.create_service("ollama", model="llama3.1")
|
||||
if ollama_service:
|
||||
prompt_ollama = "What is Ollama?"
|
||||
response_ollama = ollama_service.generate_text(prompt_ollama)
|
||||
print(f"Ollama Prompt: {prompt_ollama}")
|
||||
print(f"Ollama Response: {response_ollama}")
|
||||
else:
|
||||
print("Failed to create Ollama service.")
|
||||
except Exception as e:
|
||||
print(f"Error testing Ollama: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -844,6 +844,19 @@ async function openCSharpProjectInEditor(editor) {
|
|||
const projectId = window.csharpLauncherManager.currentProject.id;
|
||||
|
||||
try {
|
||||
// Para VS2022, primero verificar si hay archivo .sln disponible
|
||||
let solutionInfo = null;
|
||||
if (editor === 'vs2022') {
|
||||
try {
|
||||
const solutionResponse = await fetch(`/api/csharp-solution-file/${projectId}`);
|
||||
if (solutionResponse.ok) {
|
||||
solutionInfo = await solutionResponse.json();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('No se pudo verificar archivo de solución:', err);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/open-editor/${editor}/csharp/${projectId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
|
@ -852,10 +865,22 @@ async function openCSharpProjectInEditor(editor) {
|
|||
const result = await response.json();
|
||||
if (response.ok && result.status === 'success') {
|
||||
const editorName = editor === 'cursor' ? 'Cursor' : 'Visual Studio 2022';
|
||||
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
|
||||
window.csharpLauncherManager.showNotification(`${editorName} abierto exitosamente`, 'success');
|
||||
let message = `${editorName} abierto exitosamente`;
|
||||
|
||||
// Agregar información específica para VS2022
|
||||
if (editor === 'vs2022' && solutionInfo) {
|
||||
if (solutionInfo.exists && solutionInfo.solution_file) {
|
||||
const solutionName = solutionInfo.solution_file.split('\\').pop().split('/').pop();
|
||||
message = `${editorName} abierto con solución: ${solutionName}`;
|
||||
} else {
|
||||
alert(`${editorName} abierto exitosamente`);
|
||||
message = `${editorName} abierto (carpeta del proyecto)`;
|
||||
}
|
||||
}
|
||||
|
||||
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
|
||||
window.csharpLauncherManager.showNotification(message, 'success');
|
||||
} else {
|
||||
alert(message);
|
||||
}
|
||||
} else {
|
||||
const errorMsg = `Error: ${result.message}`;
|
||||
|
|
Loading…
Reference in New Issue