Feat Control Distribuidores
This commit is contained in:
@@ -13,7 +13,6 @@ def insertar_alerta_en_db(cursor, tipo_alerta, id_entidad, entidad, mensaje, fec
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
"""
|
||||
try:
|
||||
# Asegurarse de que los valores numéricos opcionales sean None si no se proporcionan
|
||||
p_dev = float(porc_devolucion) if porc_devolucion is not None else None
|
||||
c_env = int(cant_enviada) if cant_enviada is not None else None
|
||||
c_dev = int(cant_devuelta) if cant_devuelta is not None else None
|
||||
@@ -31,6 +30,7 @@ DB_DATABASE = 'SistemaGestion'
|
||||
CONNECTION_STRING = f'DRIVER={{ODBC Driver 18 for SQL Server}};SERVER={DB_SERVER};DATABASE={DB_DATABASE};Trusted_Connection=yes;TrustServerCertificate=yes;'
|
||||
MODEL_INDIVIDUAL_FILE = 'modelo_anomalias.joblib'
|
||||
MODEL_SISTEMA_FILE = 'modelo_sistema_anomalias.joblib'
|
||||
MODEL_DIST_FILE = 'modelo_anomalias_dist.joblib' # << NUEVO: Nombre del modelo de distribuidores
|
||||
|
||||
# --- 2. Determinar Fecha ---
|
||||
if len(sys.argv) > 1:
|
||||
@@ -46,11 +46,12 @@ except Exception as e:
|
||||
print(f"CRITICAL: No se pudo conectar a la base de datos. Error: {e}")
|
||||
exit()
|
||||
|
||||
# --- 3. DETECCIÓN INDIVIDUAL (CANILLITAS) ---
|
||||
# --- FASE 1: Detección de Anomalías Individuales (Canillitas) ---
|
||||
print("\n--- FASE 1: Detección de Anomalías Individuales (Canillitas) ---")
|
||||
if not os.path.exists(MODEL_INDIVIDUAL_FILE):
|
||||
print(f"ADVERTENCIA: Modelo individual '{MODEL_INDIVIDUAL_FILE}' no encontrado.")
|
||||
else:
|
||||
# ... (esta sección se mantiene exactamente igual que antes) ...
|
||||
model_individual = joblib.load(MODEL_INDIVIDUAL_FILE)
|
||||
query_individual = f"""
|
||||
SELECT esc.Id_Canilla AS id_canilla, esc.Fecha AS fecha, esc.CantSalida AS cantidad_enviada, esc.CantEntrada AS cantidad_devuelta, c.NomApe AS nombre_canilla
|
||||
@@ -81,15 +82,16 @@ else:
|
||||
cant_devuelta=row['cantidad_devuelta'],
|
||||
porc_devolucion=row['porcentaje_devolucion'])
|
||||
else:
|
||||
print("INFO: No se encontraron anomalías individuales significativas.")
|
||||
print("INFO: No se encontraron anomalías individuales significativas en canillitas.")
|
||||
else:
|
||||
print("INFO: No hay datos de canillitas para analizar en la fecha seleccionada.")
|
||||
|
||||
# --- 4. DETECCIÓN DE SISTEMA ---
|
||||
# --- FASE 2: Detección de Anomalías de Sistema ---
|
||||
print("\n--- FASE 2: Detección de Anomalías de Sistema ---")
|
||||
if not os.path.exists(MODEL_SISTEMA_FILE):
|
||||
print(f"ADVERTENCIA: Modelo de sistema '{MODEL_SISTEMA_FILE}' no encontrado.")
|
||||
else:
|
||||
# ... (esta sección se mantiene exactamente igual que antes) ...
|
||||
model_sistema = joblib.load(MODEL_SISTEMA_FILE)
|
||||
query_agregada = f"""
|
||||
SELECT CAST(Fecha AS DATE) AS fecha_dia, DATEPART(weekday, Fecha) as dia_semana,
|
||||
@@ -128,7 +130,59 @@ else:
|
||||
mensaje=mensaje,
|
||||
fecha_anomalia=target_date.date())
|
||||
|
||||
# --- 5. Finalización ---
|
||||
# --- FASE 3: Detección de Anomalías Individuales (Distribuidores) ---
|
||||
print("\n--- FASE 3: Detección de Anomalías Individuales (Distribuidores) ---")
|
||||
if not os.path.exists(MODEL_DIST_FILE):
|
||||
print(f"ADVERTENCIA: Modelo de distribuidores '{MODEL_DIST_FILE}' no encontrado.")
|
||||
else:
|
||||
model_dist = joblib.load(MODEL_DIST_FILE)
|
||||
query_dist = f"""
|
||||
SELECT
|
||||
es.Id_Distribuidor AS id_distribuidor,
|
||||
d.Nombre AS nombre_distribuidor,
|
||||
CAST(es.Fecha AS DATE) AS fecha,
|
||||
SUM(CASE WHEN es.TipoMovimiento = 'Salida' THEN es.Cantidad ELSE 0 END) as cantidad_enviada,
|
||||
SUM(CASE WHEN es.TipoMovimiento = 'Entrada' THEN es.Cantidad ELSE 0 END) as cantidad_devuelta
|
||||
FROM
|
||||
dist_EntradasSalidas es
|
||||
JOIN
|
||||
dist_dtDistribuidores d ON es.Id_Distribuidor = d.Id_Distribuidor
|
||||
WHERE
|
||||
CAST(es.Fecha AS DATE) = '{target_date.strftime('%Y-%m-%d')}'
|
||||
GROUP BY
|
||||
es.Id_Distribuidor, d.Nombre, CAST(es.Fecha AS DATE)
|
||||
HAVING
|
||||
SUM(CASE WHEN es.TipoMovimiento = 'Salida' THEN es.Cantidad ELSE 0 END) > 0
|
||||
"""
|
||||
df_dist_new = pd.read_sql(query_dist, cnxn)
|
||||
|
||||
if not df_dist_new.empty:
|
||||
df_dist_new['porcentaje_devolucion'] = (df_dist_new['cantidad_devuelta'] / df_dist_new['cantidad_enviada']).fillna(0) * 100
|
||||
df_dist_new['dia_semana'] = pd.to_datetime(df_dist_new['fecha']).dt.dayofweek
|
||||
features_dist = ['id_distribuidor', 'porcentaje_devolucion', 'dia_semana']
|
||||
X_dist_new = df_dist_new[features_dist]
|
||||
df_dist_new['anomalia'] = model_dist.predict(X_dist_new)
|
||||
|
||||
anomalias_dist_detectadas = df_dist_new[df_dist_new['anomalia'] == -1]
|
||||
|
||||
if not anomalias_dist_detectadas.empty:
|
||||
for index, row in anomalias_dist_detectadas.iterrows():
|
||||
mensaje = f"Devolución inusual del {row['porcentaje_devolucion']:.2f}% para el distribuidor '{row['nombre_distribuidor']}'."
|
||||
insertar_alerta_en_db(cursor,
|
||||
tipo_alerta='DevolucionAnomalaDist',
|
||||
id_entidad=row['id_distribuidor'],
|
||||
entidad='Distribuidor',
|
||||
mensaje=mensaje,
|
||||
fecha_anomalia=row['fecha'],
|
||||
cant_enviada=row['cantidad_enviada'],
|
||||
cant_devuelta=row['cantidad_devuelta'],
|
||||
porc_devolucion=row['porcentaje_devolucion'])
|
||||
else:
|
||||
print("INFO: No se encontraron anomalías individuales significativas en distribuidores.")
|
||||
else:
|
||||
print("INFO: No hay datos de distribuidores para analizar en la fecha seleccionada.")
|
||||
|
||||
# --- Finalización ---
|
||||
cnxn.commit()
|
||||
cnxn.close()
|
||||
print("\n--- DETECCIÓN COMPLETA ---")
|
||||
BIN
ProyectoIA_Gestion/modelo_anomalias_dist.joblib
Normal file
BIN
ProyectoIA_Gestion/modelo_anomalias_dist.joblib
Normal file
Binary file not shown.
69
ProyectoIA_Gestion/train_distribuidores.py
Normal file
69
ProyectoIA_Gestion/train_distribuidores.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import pandas as pd
|
||||
from sklearn.ensemble import IsolationForest
|
||||
import joblib
|
||||
import pyodbc
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
print("--- INICIANDO SCRIPT DE ENTRENAMIENTO (DISTRIBUIDORES) ---")
|
||||
|
||||
# --- 1. Configuración ---
|
||||
DB_SERVER = 'TECNICA3'
|
||||
DB_DATABASE = 'SistemaGestion'
|
||||
CONNECTION_STRING = f'DRIVER={{ODBC Driver 18 for SQL Server}};SERVER={DB_SERVER};DATABASE={DB_DATABASE};Trusted_Connection=yes;TrustServerCertificate=yes;'
|
||||
|
||||
MODEL_FILE = 'modelo_anomalias_dist.joblib' # <-- Nuevo nombre de archivo para el modelo
|
||||
CONTAMINATION_RATE = 0.01 # Un 1% de contaminación es un buen punto de partida
|
||||
|
||||
# --- 2. Carga de Datos desde SQL Server ---
|
||||
try:
|
||||
print(f"Conectando a la base de datos '{DB_DATABASE}' en '{DB_SERVER}'...")
|
||||
cnxn = pyodbc.connect(CONNECTION_STRING)
|
||||
|
||||
fecha_limite = datetime.now() - timedelta(days=365)
|
||||
|
||||
# << CAMBIO IMPORTANTE: Nueva consulta para agrupar Salidas y Entradas por distribuidor y día >>
|
||||
query = f"""
|
||||
SELECT
|
||||
Id_Distribuidor AS id_distribuidor,
|
||||
CAST(Fecha AS DATE) AS fecha,
|
||||
SUM(CASE WHEN TipoMovimiento = 'Salida' THEN Cantidad ELSE 0 END) as cantidad_enviada,
|
||||
SUM(CASE WHEN TipoMovimiento = 'Entrada' THEN Cantidad ELSE 0 END) as cantidad_devuelta
|
||||
FROM
|
||||
dist_EntradasSalidas
|
||||
WHERE
|
||||
Fecha >= '{fecha_limite.strftime('%Y-%m-%d')}'
|
||||
GROUP BY
|
||||
Id_Distribuidor, CAST(Fecha AS DATE)
|
||||
HAVING
|
||||
SUM(CASE WHEN TipoMovimiento = 'Salida' THEN Cantidad ELSE 0 END) > 0
|
||||
"""
|
||||
print("Ejecutando consulta para obtener datos de entrenamiento de distribuidores...")
|
||||
df = pd.read_sql(query, cnxn)
|
||||
cnxn.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error al conectar o consultar la base de datos: {e}")
|
||||
exit()
|
||||
|
||||
if df.empty:
|
||||
print("No se encontraron datos de entrenamiento de distribuidores en el último año. Saliendo.")
|
||||
exit()
|
||||
|
||||
# --- 3. Preparación de Datos ---
|
||||
print(f"Preparando {len(df)} registros para el entrenamiento del modelo de distribuidores...")
|
||||
# Se usa (df['cantidad_enviada'] + 0.001) para evitar división por cero
|
||||
df['porcentaje_devolucion'] = (df['cantidad_devuelta'] / (df['cantidad_enviada'] + 0.001)) * 100
|
||||
df.fillna(0, inplace=True)
|
||||
df['porcentaje_devolucion'] = df['porcentaje_devolucion'].clip(0, 100)
|
||||
df['dia_semana'] = pd.to_datetime(df['fecha']).dt.dayofweek
|
||||
|
||||
features = ['id_distribuidor', 'porcentaje_devolucion', 'dia_semana'] # <-- Característica clave: id_distribuidor
|
||||
X = df[features]
|
||||
|
||||
# --- 4. Entrenamiento y Guardado ---
|
||||
print(f"Entrenando el modelo con tasa de contaminación de {CONTAMINATION_RATE}...")
|
||||
model = IsolationForest(n_estimators=100, contamination=CONTAMINATION_RATE, random_state=42)
|
||||
model.fit(X)
|
||||
joblib.dump(model, MODEL_FILE)
|
||||
|
||||
print(f"--- ENTRENAMIENTO DE DISTRIBUIDORES COMPLETADO. Modelo guardado en '{MODEL_FILE}' ---")
|
||||
Reference in New Issue
Block a user