Compare commits
1 Commits
main
...
voe-sapian
| Author | SHA1 | Date |
|---|---|---|
|
|
8859f04ada | 1 year ago |
@ -0,0 +1,192 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Interfaz de Servicio</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
background-color: #f4f4f9;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.logo {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
width: 150px;
|
||||
height: auto;
|
||||
}
|
||||
.logoS {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 20px;
|
||||
width: 150px;
|
||||
height: auto;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
input, button, textarea {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Control de Servicio</h1>
|
||||
<img src="Logo_sian_all.png" alt="Logo de la Empresa" class="logo">
|
||||
<img src="SAPIANLOGO.png" alt="Logo de la Empresa" class="logoS">
|
||||
<!-- Campo de rango de cumplimiento -->
|
||||
<label for="complianceRange">Rango de Cumplimiento</label>
|
||||
<textarea id="complianceRange" rows="4" placeholder="Ingresa los parametros para la clasificación de las llamadas"></textarea>
|
||||
<button id="sendButton">Enviar</button>
|
||||
|
||||
<!-- prompt de backoffice -->
|
||||
<label for="backoffice">Prompt Backoffice</label>
|
||||
<textarea id="backoffice" rows="4" placeholder="Ingresa los parámetros que quieres conocer de las llamadas del mes"></textarea>
|
||||
<button id="sendBack">Enviar</button>
|
||||
|
||||
<!-- Campo para BackOffice del mes -->
|
||||
<label for="monthInput">BackOffice del mes</label>
|
||||
<input type="number" id="monthInput" min="1" max="12" placeholder="Ingrese un número del 1 al 12">
|
||||
<button id="generateButton">Generar</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Función para cargar datos iniciales
|
||||
function loadInitialData() {
|
||||
fetch('http://resumen-sapian-cctel-02.dialbox.cloud:5024/get_initial_data')
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw new Error('Error al obtener los datos iniciales');
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
// Establecer valores en los campos del HTML
|
||||
document.getElementById('complianceRange').value = data.rango_cumplimiento || "";
|
||||
document.getElementById('backoffice').value = data.promt_back || "";
|
||||
alert('Datos cargados correctamente.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error al cargar los datos iniciales:', error);
|
||||
alert('Error al cargar los datos iniciales.');
|
||||
});
|
||||
}
|
||||
|
||||
// Llamar a la función al cargar la página
|
||||
document.addEventListener('DOMContentLoaded', loadInitialData);
|
||||
|
||||
// Enviar rango de cumplimiento
|
||||
document.getElementById('sendButton').addEventListener('click', function () {
|
||||
const complianceText = document.getElementById('complianceRange').value;
|
||||
fetch('http://resumen-sapian-cctel-02.dialbox.cloud:5021/rango_cumplimiento', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ text: complianceText })
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert('Rango de cumplimiento guardado correctamente.');
|
||||
} else {
|
||||
response.text().then(text => {
|
||||
alert(`Error al guardar el rango de cumplimiento: ${text}`);
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error de red:', error);
|
||||
alert('No se pudo conectar al servidor.');
|
||||
});
|
||||
});
|
||||
|
||||
// Enviar prompt backoffice
|
||||
document.getElementById('sendBack').addEventListener('click', function () {
|
||||
const complianceText = document.getElementById('backoffice').value;
|
||||
fetch('http://resumen-sapian-cctel-02.dialbox.cloud:5022/pront_back', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ text: complianceText })
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert('Prompt guardado correctamente.');
|
||||
} else {
|
||||
response.text().then(text => {
|
||||
alert(`Error al guardar el prompt: ${text}`);
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error de red:', error);
|
||||
alert('No se pudo conectar al servidor.');
|
||||
});
|
||||
});
|
||||
|
||||
// Generar archivo para el mes
|
||||
document.getElementById('generateButton').addEventListener('click', function () {
|
||||
const month = document.getElementById('monthInput').value;
|
||||
alert('el mes seleccionado es' + month);
|
||||
|
||||
if (month < 1 || month > 12) {
|
||||
alert('Por favor, ingrese un mes válido entre 1 y 12.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('http://resumen-sapian-cctel-02.dialbox.cloud:5000/execute_script', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ month: month })
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
const url = `http://resumen-sapian-cctel-02.dialbox.cloud/back_de_mes_sapian_2024-${month}.html`;
|
||||
alert(`Se generó el resumen en: ${url}`);
|
||||
} else {
|
||||
response.text().then(text => {
|
||||
alert(`Error al ejecutar el script: ${text}`);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error de red:', error);
|
||||
alert('No se pudo conectar al servidor.');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -0,0 +1,136 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import anthropic
|
||||
from datetime import datetime
|
||||
|
||||
# Ruta donde están los archivos
|
||||
ruta_archivos = "/var/www/html"
|
||||
# Patrón para archivos con fechas en el nombre
|
||||
patron_fecha = re.compile(r"resumen_sapian_(\d{4}-\d{2}-\d{2})")
|
||||
|
||||
# Verificar si el usuario pasó el mes como argumento
|
||||
if len(sys.argv) != 2:
|
||||
print("Uso: python3 concatenar_archivos.py <mes>")
|
||||
print("Ejemplo: python3 concatenar_archivos.py 11")
|
||||
sys.exit(1)
|
||||
|
||||
mes_argumento = sys.argv[1]
|
||||
|
||||
# Validar el argumento del mes
|
||||
if not mes_argumento.isdigit() or not (1 <= int(mes_argumento) <= 12):
|
||||
print("El mes debe ser un número entre 01 y 12.")
|
||||
sys.exit(1)
|
||||
|
||||
# Formatear el mes como dos dígitos
|
||||
mes_argumento = mes_argumento.zfill(2)
|
||||
|
||||
# Obtener el año actual para construir el filtro de archivos
|
||||
anio_actual = datetime.now().year
|
||||
|
||||
# Lista para almacenar archivos que coinciden con el mes especificado
|
||||
archivos_mes = []
|
||||
|
||||
# Recorrer los archivos en la ruta
|
||||
for archivo in os.listdir(ruta_archivos):
|
||||
match = patron_fecha.search(archivo)
|
||||
if match:
|
||||
fecha_str = match.group(1)
|
||||
fecha = datetime.strptime(fecha_str, "%Y-%m-%d")
|
||||
if fecha.strftime("%Y-%m") == f"{anio_actual}-{mes_argumento}":
|
||||
archivos_mes.append(os.path.join(ruta_archivos, archivo))
|
||||
|
||||
# Verificar si se encontraron archivos
|
||||
if not archivos_mes:
|
||||
print(f"No se encontraron archivos para el mes {mes_argumento} del año {anio_actual}.")
|
||||
sys.exit(0)
|
||||
|
||||
# Archivo de salida
|
||||
archivo_salida = os.path.join(ruta_archivos, f"resumen_sapian_{anio_actual}-{mes_argumento}.txt")
|
||||
|
||||
# Concatenar el contenido de todos los archivos
|
||||
contenido_total = ""
|
||||
for archivo in archivos_mes:
|
||||
with open(archivo, "r") as entrada:
|
||||
contenido_total += entrada.read() + "\n"
|
||||
|
||||
# Eliminar todas las etiquetas HTML
|
||||
contenido_sin_html = re.sub(r"<[^>]*>", "", contenido_total)
|
||||
|
||||
# Eliminar el bloque CSS repetido. Suponiendo que el CSS siempre está entre
|
||||
# 'body {' y la siguiente "Resumen de Llamada".
|
||||
# Esta expresión regular busca "body {", consume todo hasta el siguiente "Resumen de Llamada"
|
||||
# y lo reemplaza por nada.
|
||||
contenido_sin_css = re.sub(r"(?s)body\s*\{.*?Resumen de Llamada", "Resumen de Llamada", contenido_sin_html)
|
||||
|
||||
# También podrías optar por eliminar cualquier otro bloque CSS conocido. Por ejemplo, si tu CSS
|
||||
# siempre va desde `body {` hasta antes del siguiente `Resumen de Llamada`, el anterior regex debería funcionar.
|
||||
# Si tu CSS es exactamente el mismo en todas partes, podrías usar una expresión más específica.
|
||||
|
||||
# Limpiar espacios en blanco excesivos
|
||||
contenido_limpio = re.sub(r"\s+\n", "\n", contenido_sin_css).strip()
|
||||
|
||||
# Guardar el resultado limpio en el archivo final
|
||||
with open(archivo_salida, "w") as salida:
|
||||
salida.write(contenido_limpio)
|
||||
|
||||
print(f"Archivos del mes {mes_argumento} concatenados sin HTML/CSS en: {archivo_salida}")
|
||||
|
||||
def consultar_claude(text):
|
||||
|
||||
ruta_archivo = "/home/promt_back.txt"
|
||||
|
||||
# Leer el contenido del archivo y asignarlo a un string
|
||||
try:
|
||||
with open(ruta_archivo, "r") as archivo:
|
||||
contenido = archivo.read()
|
||||
except FileNotFoundError:
|
||||
print(f"El archivo {ruta_archivo} no existe.")
|
||||
contenido = ""
|
||||
except Exception as e:
|
||||
print(f"Ocurrió un error al leer el archivo: {e}")
|
||||
contenido = ""
|
||||
|
||||
client = anthropic.Anthropic(api_key='sk-ant-')
|
||||
message = client.messages.create(
|
||||
model="claude-3-5-haiku-20241022",
|
||||
max_tokens=5000,
|
||||
temperature=0,
|
||||
system=(
|
||||
"vas a recibir el siguiente archivo con varias llamadas, debes devolver la respuesta en HTML para una pagina con tablas CSS. De cada petición devuélvemela formateada en HTML. Lo que devuelvas será escrito en una página con la siguiente peticion:"
|
||||
+ contenido
|
||||
),
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "text", "text": text}]
|
||||
}
|
||||
]
|
||||
)
|
||||
respuesta_claude = "".join([msg.text for msg in message.content])
|
||||
|
||||
# Procesar para agregar la clase TEMA a todas las celdas correspondientes
|
||||
respuesta_claude = respuesta_claude.replace("<td>", "<td class='TEMA'>")
|
||||
return respuesta_claude
|
||||
|
||||
try:
|
||||
with open(archivo_salida, "r") as archivo:
|
||||
contenido = archivo.read()
|
||||
except FileNotFoundError:
|
||||
print(f"El archivo {archivo_salida} no existe.")
|
||||
contenido = ""
|
||||
except Exception as e:
|
||||
print(f"Ocurrió un error al leer el archivo: {e}")
|
||||
contenido = ""
|
||||
|
||||
resp_claude = consultar_claude(contenido)
|
||||
|
||||
archivo_salida_html = os.path.join(ruta_archivos, f"back_de_mes_sapian_{anio_actual}-{mes_argumento}.html")
|
||||
|
||||
try:
|
||||
with open(archivo_salida_html, "w") as archivo:
|
||||
archivo.write(resp_claude)
|
||||
print(f"Archivo creado exitosamente en: {archivo_salida_html}")
|
||||
except Exception as e:
|
||||
print(f"Error al crear el archivo: {e}")
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Verificar si se pasó un argumento
|
||||
if [ -z "$1" ]; then
|
||||
echo "Por favor, proporciona un argumento: 1 (start) o 0 (stop)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ejecutar el comando basado en el argumento
|
||||
if [ "$1" -eq 0 ]; then
|
||||
echo "Deteniendo el servicio resumen_endpoint.service"
|
||||
systemctl stop resumen_endpoint.service
|
||||
elif [ "$1" -eq 1 ]; then
|
||||
echo "Iniciando el servicio resumen_endpoint.service"
|
||||
systemctl start resumen_endpoint.service
|
||||
else
|
||||
echo "Argumento inválido. Usa 1 para iniciar o 0 para detener."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
import subprocess
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app, resources={r"/*": {"origins": "http://resumen-sapian-cctel-02.dialbox.cloud"}})
|
||||
|
||||
|
||||
|
||||
@app.route('/execute_script', methods=['POST'])
|
||||
def execute_script():
|
||||
try:
|
||||
data = request.get_json()
|
||||
month = data.get('month')
|
||||
|
||||
if not month or not (1 <= int(month) <= 12):
|
||||
return jsonify({"error": "Mes inválido. Debe estar entre 1 y 12."}), 400
|
||||
|
||||
script_path = "/home/concatenar_archivos.py"
|
||||
command = ["python3", script_path, str(month)]
|
||||
|
||||
result = subprocess.run(command, capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
return jsonify({"message": "Script ejecutado correctamente.", "output": result.stdout}), 200
|
||||
else:
|
||||
return jsonify({"error": "Error al ejecutar el script.", "details": result.stderr}), 500
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Error en el servidor.", "details": str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
import subprocess
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app, resources={r"/*": {"origins": "http://resumen-sapian-cctel-02.dialbox.cloud"}})
|
||||
|
||||
|
||||
|
||||
@app.route('/execute_script', methods=['POST'])
|
||||
def execute_script():
|
||||
try:
|
||||
data = request.get_json()
|
||||
month = data.get('month')
|
||||
|
||||
if not month or not (1 <= int(month) <= 12):
|
||||
return jsonify({"error": "Mes inválido. Debe estar entre 1 y 12."}), 400
|
||||
|
||||
script_path = "/home/concatenar_archivos.py"
|
||||
command = ["python3", script_path, str(month)]
|
||||
|
||||
result = subprocess.run(command, capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
return jsonify({"message": "Script ejecutado correctamente.", "output": result.stdout}), 200
|
||||
else:
|
||||
return jsonify({"error": "Error al ejecutar el script.", "details": result.stderr}), 500
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Error en el servidor.", "details": str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app) # Habilitar CORS para permitir solicitudes
|
||||
|
||||
@app.route('/save_compliance_range', methods=['POST'])
|
||||
def save_compliance_range():
|
||||
try:
|
||||
# Obtener el texto enviado en la solicitud
|
||||
data = request.get_json()
|
||||
text = data.get('text')
|
||||
|
||||
# Validar que el texto no esté vacío
|
||||
if not text:
|
||||
return jsonify({"error": "El texto no puede estar vacío."}), 400
|
||||
|
||||
# Ruta del archivo donde se guardará el texto
|
||||
file_path = "/home/rango_llamada_unica.txt"
|
||||
|
||||
# Escribir el texto en el archivo
|
||||
with open(file_path, 'w') as file:
|
||||
file.write(text)
|
||||
|
||||
return jsonify({"message": "Texto guardado correctamente en rango_cumplimiento.txt"}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Error al guardar el texto.", "details": str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5022)
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app, resources={r"/*": {"origins": "http://resumen-sapian-cctel-02.dialbox.cloud"}})
|
||||
|
||||
@app.route('/rango_cumplimiento', methods=['POST'])
|
||||
def save_compliance_range():
|
||||
try:
|
||||
# Obtener el texto enviado en la solicitud
|
||||
data = request.get_json()
|
||||
text = data.get('text')
|
||||
|
||||
# Validar que el texto no esté vacío
|
||||
if not text:
|
||||
return jsonify({"error": "El texto no puede estar vacío."}), 400
|
||||
|
||||
# Ruta del archivo donde se guardará el texto
|
||||
file_path = "/home/rango_cumplimiento.txt"
|
||||
|
||||
# Escribir el texto en el archivo
|
||||
with open(file_path, 'w') as file:
|
||||
file.write(text)
|
||||
|
||||
return jsonify({"message": "Texto guardado correctamente en rango_cumplimiento.txt"}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Error al guardar el texto.", "details": str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5021)
|
||||
|
||||
@ -0,0 +1 @@
|
||||
Dame una tabla con la calificacion de todos los agentes[
|
||||
@ -0,0 +1,32 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app, resources={r"/*": {"origins": "http://resumen-sapian-cctel-02.dialbox.cloud"}})
|
||||
|
||||
@app.route('/pront_back', methods=['POST'])
|
||||
def save_compliance_range():
|
||||
try:
|
||||
# Obtener el texto enviado en la solicitud
|
||||
data = request.get_json()
|
||||
text = data.get('text')
|
||||
|
||||
# Validar que el texto no esté vacío
|
||||
if not text:
|
||||
return jsonify({"error": "El texto no puede estar vacío."}), 400
|
||||
|
||||
# Ruta del archivo donde se guardará el texto
|
||||
file_path = "/home/promt_back.txt"
|
||||
|
||||
# Escribir el texto en el archivo
|
||||
with open(file_path, 'w') as file:
|
||||
file.write(text)
|
||||
|
||||
return jsonify({"message": "Texto guardado correctamente en promt_back.txt"}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Error al guardar el texto.", "details": str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5022)
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
Presentación saludo de acuerdo al protocolo
|
||||
|
||||
Presentación y saludo de acuerdo a los siguientes momentos: 1. Informar
|
||||
del departamento que se comunica, 2. posicionamiento de marca, 3.
|
||||
presentación personal. Ej: Buenos días bienvenido al área de (soporte TIC
|
||||
- Mesa Sura- Servicios de telefonia) mi nombre es xxxx, con quien tengo
|
||||
el gusto de hablar?.// Buenas tardes nos estamos comunicando del área
|
||||
de (soporte TIC - Mesa Sura- Servicios de telefonia), mi nombre es xxx,
|
||||
tengo el gusto de hablar con el señor@ xxxx.
|
||||
|
||||
E.C salida / E.N.C entrada, habilidad blanda 15%
|
||||
|
||||
Escucha activa
|
||||
|
||||
Identificar la necesidad real del cliente permitiendo que se exprese con
|
||||
tranquilidad, evitando interrumpirlo o hacerlo repetir la información,
|
||||
dando respuestas coherentes a la conversación.
|
||||
|
||||
E.C en todas 20%
|
||||
|
||||
Fluidez verbal, dicción y tono de voz no muletillas, tono de voz dinamico, amable que logre captar la atención
|
||||
|
||||
del cliente y que tenga buena pronunciación E.N.C habilidad blanda 10%
|
||||
|
||||
Genera credibilidad y confianza
|
||||
|
||||
Generar empatía, transmitiendo al cliente que le importa lo que está
|
||||
hablando, transmite seguridad, sin intimidar al cliente, generando un
|
||||
trato cordial y amable durante toda la llamada
|
||||
|
||||
E.N.C habilidad blanda 15%
|
||||
|
||||
Acompañamiento
|
||||
|
||||
Contesta inmediatamente ingresa la llamada, informa al cliente que lo
|
||||
dejara en silencio y retoma constantemente la llamada con el fin de
|
||||
|
||||
evitar que el cliente se sienta solo.
|
||||
|
||||
E.N.C habilidad blanda/ E.C SI EL CLIENTE
|
||||
FINALIZA LA LLAMADA POR FALTA DE ATENCIÓN 10%
|
||||
|
||||
Tipificación de la llamada Utiliza un tiempo prudente para finalizar la llamada (si el cliente colgó
|
||||
|
||||
primero) y para tipificarla? tipificación máxima 3 segundos. E.C en todas 15%
|
||||
|
||||
Cierre de llamada de acuerdo al protocolo establecido
|
||||
|
||||
El auxiliar debe confirmar al cliente la información brindada, que haya
|
||||
sido clara y guión despedida, 1. agradecer la atención 2. despedirse//
|
||||
gracias por comunicarse con nosotros que tenga un feliz dia// gracias por
|
||||
atender nuestra llamada que tenga un feliz dia.
|
||||
|
||||
E.C en todas 15%
|
||||
100 %
|
||||
|
||||
@ -0,0 +1,244 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import pymysql
|
||||
import tempfile
|
||||
from pydub import AudioSegment
|
||||
import speech_recognition as sr
|
||||
import anthropic
|
||||
|
||||
def consultar_claude(text):
|
||||
|
||||
ruta_archivo = "/home/promt_back.txt"
|
||||
|
||||
# Leer el contenido del archivo y asignarlo a un string
|
||||
try:
|
||||
with open(ruta_archivo, "r") as archivo:
|
||||
contenido = archivo.read()
|
||||
except FileNotFoundError:
|
||||
print(f"El archivo {ruta_archivo} no existe.")
|
||||
except Exception as e:
|
||||
print(f"Ocurrió un error al leer el archivo: {e}")
|
||||
|
||||
|
||||
client = anthropic.Anthropic(api_key='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')
|
||||
message = client.messages.create(
|
||||
model="claude-3-5-haiku-20241022",
|
||||
#model = "claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
temperature=0,
|
||||
system=(
|
||||
"A continuación te paso el texto de una llamada y necesito un resumen completo que incluyas la calificacion del agentes con los siguientes parametros:" + contenido + "si la llamada no se puede identificar o resumir solo devuelve 4 asteriscos seguidos ****"
|
||||
),
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "text", "text": text}]
|
||||
}
|
||||
]
|
||||
)
|
||||
respuesta_claude = "".join([msg.text for msg in message.content])
|
||||
|
||||
# Procesar para agregar la clase TEMA a todas las celdas correspondientes
|
||||
respuesta_claude = respuesta_claude.replace("<td>", "<td class='TEMA'>")
|
||||
return respuesta_claude
|
||||
|
||||
|
||||
def get_filename_from_db(recording_id):
|
||||
connection = pymysql.connect(
|
||||
host='sapian-ccdb-clients-sapian.service.dc-k8s-voe.consul',
|
||||
user='cctel',
|
||||
password='PpteK0GYyi',
|
||||
database='asterisk'
|
||||
)
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
query = "SELECT filename FROM recording_log WHERE recording_id = %s"
|
||||
cursor.execute(query, (recording_id,))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return result[0]
|
||||
else:
|
||||
raise ValueError("No se encontró la grabación con el recording_id proporcionado.")
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
#def split_audio(file_path, output_folder):
|
||||
# audio = AudioSegment.from_file(file_path)
|
||||
# duration = len(audio) # Duration in milliseconds
|
||||
# chunk_duration = 60 * 1000 # 1 minute in milliseconds
|
||||
# chunks = []
|
||||
# for i in range(0, duration, chunk_duration):
|
||||
# chunk = audio[i:i + chunk_duration]
|
||||
# chunk_name = os.path.join(output_folder, f"chunk_{i//chunk_duration + 1}.wav")
|
||||
# chunk.export(chunk_name, format="wav")
|
||||
# chunks.append(chunk_name)
|
||||
# return chunks
|
||||
|
||||
def split_audio(file_path, output_folder):
|
||||
audio = AudioSegment.from_file(file_path)
|
||||
duration = len(audio) # Duration in milliseconds
|
||||
chunk_duration = 60 * 1000 # 1 minute in milliseconds
|
||||
min_duration = 30 * 1000 # 30 seconds in milliseconds
|
||||
chunks = []
|
||||
|
||||
for i in range(0, duration, chunk_duration):
|
||||
chunk = audio[i:i + chunk_duration]
|
||||
|
||||
# Verificar si la duración del fragmento es menor a 30 seg
|
||||
if len(chunk) < min_duration:
|
||||
print("El último fragmento es menor a 30 segundos. Terminando el programa.")
|
||||
sys.exit(0)
|
||||
|
||||
chunk_name = os.path.join(output_folder, f"chunk_{(i//chunk_duration) + 1}.wav")
|
||||
chunk.export(chunk_name, format="wav")
|
||||
chunks.append(chunk_name)
|
||||
|
||||
return chunks
|
||||
|
||||
def transcribe_audio_chunks(chunks):
|
||||
recognizer = sr.Recognizer()
|
||||
transcription = []
|
||||
for idx, chunk in enumerate(chunks, 1):
|
||||
with sr.AudioFile(chunk) as source:
|
||||
audio_data = recognizer.record(source)
|
||||
try:
|
||||
text = recognizer.recognize_google(audio_data, language="es-ES")
|
||||
transcription.append(f"Fragmento {idx}: {text}")
|
||||
except sr.UnknownValueError:
|
||||
transcription.append(f"Fragmento {idx}: No se pudo reconocer el audio")
|
||||
except sr.RequestError as e:
|
||||
transcription.append(f"Fragmento {idx}: Error en el servicio de reconocimiento ({e})")
|
||||
return "\n".join(transcription)
|
||||
|
||||
def generate_html_summary(summary, output_path, fecha_hora):
|
||||
# Define todo el CSS en una sola variable, incluyendo el estilo para las dos primeras filas
|
||||
css_styles = """
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
margin-top: 50px;
|
||||
}
|
||||
p {
|
||||
font-size: 18px;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
text-align: justify;
|
||||
}
|
||||
.logo {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
width: 250px;
|
||||
height: auto;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0px;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: white;
|
||||
border: 2px solid #004d40;
|
||||
padding: 20px;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background-color: #004d40ff;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 25px;
|
||||
}
|
||||
td.TEMA {
|
||||
font-weight: bold;
|
||||
}
|
||||
tr:nth-child(1) {
|
||||
background-color: #4CAF50; /* Verde para las dos primeras filas */
|
||||
color: white; /* Texto en blanco para contraste */
|
||||
}
|
||||
tr:nth-child(even):not(:nth-child(1)):not(:nth-child(2)) {
|
||||
background-color: white;
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
"""
|
||||
|
||||
# Genera el contenido HTML
|
||||
html_content = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Resumen de Llamada</title>
|
||||
<style>
|
||||
{css_styles}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="SAPIANLOGO.png" alt="Logo de la Empresa" class="logo">
|
||||
<h1>Resumen de Llamada</h1>
|
||||
<h2 id="fecha">{fecha_hora}</h2>
|
||||
<table>
|
||||
{summary}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Escribir el archivo HTML en el directorio deseado
|
||||
with open(output_path, "w") as file:
|
||||
file.write(html_content)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("Uso: python script.py <recording_id>")
|
||||
sys.exit(1)
|
||||
|
||||
recording_id = sys.argv[1]
|
||||
|
||||
|
||||
fecha_hora_actual = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
try:
|
||||
filename = get_filename_from_db(recording_id)
|
||||
audio_path = f"/var/spool/asterisk/monitor/{filename}"+"-in.WAV"
|
||||
|
||||
if not os.path.exists(audio_path):
|
||||
raise FileNotFoundError(f"No se encontró la grabación en la ruta: {audio_path}")
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
output_folder = os.path.join(temp_dir, "audio_chunks")
|
||||
os.makedirs(output_folder, exist_ok=True)
|
||||
|
||||
chunks = split_audio(audio_path, output_folder)
|
||||
transcription = transcribe_audio_chunks(chunks)
|
||||
resumen = consultar_claude(transcription)
|
||||
if resumen == "****":
|
||||
sys.exit()
|
||||
|
||||
fecha_hora_actual = fecha_hora_actual.replace(" ", "")
|
||||
output_html = f"/var/www/html/resumen_sapian_{fecha_hora_actual}_{recording_id}.html"
|
||||
generate_html_summary(resumen, output_html, fecha_hora_actual)
|
||||
|
||||
print(f"Proceso completado. Resumen generado en: {output_html}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
/usr/bin/python3 /home/resumen_endpoint.py &
|
||||
/usr/bin/python3 /home/resumen_endpoint_sapian.py &
|
||||
/usr/bin/python3 /home/promt_backoffice.py &
|
||||
/usr/bin/python3 /home/endpoint_rango_cumplimiento.py &
|
||||
/usr/bin/python3 /home/set_data.py &
|
||||
/usr/bin/python3 /home/endpoint_concatenar.py
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
import subprocess
|
||||
import urllib.parse
|
||||
|
||||
# Configurar el puerto del servidor
|
||||
PORT = 8889
|
||||
|
||||
class RequestHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
# Parsear los parámetros de la URL
|
||||
parsed_url = urllib.parse.urlparse(self.path)
|
||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||
|
||||
# Obtener el parámetro recording_id
|
||||
recording_id = query_params.get('recording_id', [None])[0]
|
||||
|
||||
if recording_id:
|
||||
# Ejecutar el script con el parámetro recording_id
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["sudo", "python3", "/home/resume_end_call_sapian.py", recording_id],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
# Concatenar stdout y stderr
|
||||
output = f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
|
||||
# Responder con el resultado
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write(result.stdout.encode())
|
||||
except Exception as e:
|
||||
# Manejar errores de ejecución
|
||||
self.send_response(500)
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write(f"Error executing script: {str(e)}".encode())
|
||||
else:
|
||||
# Manejar el caso de parámetro faltante
|
||||
self.send_response(400)
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Missing 'recording_id' parameter")
|
||||
|
||||
# Configurar y ejecutar el servidor
|
||||
def run(server_class=HTTPServer, handler_class=RequestHandler):
|
||||
server_address = ("", PORT)
|
||||
httpd = server_class(server_address, handler_class)
|
||||
print(f"Server running on port {PORT}...")
|
||||
httpd.serve_forever()
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
from flask import Flask, jsonify
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app, resources={r"/*": {"origins": "http://resumen-sapian-cctel-02.dialbox.cloud"}})
|
||||
|
||||
|
||||
|
||||
@app.route('/get_initial_data', methods=['GET'])
|
||||
def get_initial_data():
|
||||
try:
|
||||
# Ruta de los archivos
|
||||
promt_file = "/home/promt_back.txt"
|
||||
range_file = "/home/rango_cumplimiento.txt"
|
||||
|
||||
# Leer contenido de los archivos
|
||||
promt_content = ""
|
||||
range_content = ""
|
||||
|
||||
try:
|
||||
with open(promt_file, 'r') as file:
|
||||
promt_content = file.read()
|
||||
except FileNotFoundError:
|
||||
promt_content = "Archivo promt_back.txt no encontrado."
|
||||
|
||||
try:
|
||||
with open(range_file, 'r') as file:
|
||||
range_content = file.read()
|
||||
except FileNotFoundError:
|
||||
range_content = "Archivo rango_cumplimiento.txt no encontrado."
|
||||
|
||||
# Devolver los contenidos en un JSON
|
||||
return jsonify({
|
||||
"promt_back": promt_content,
|
||||
"rango_cumplimiento": range_content
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Error al leer los archivos.", "details": str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5024)
|
||||
|
||||
Loading…
Reference in new issue