Compare commits
3 Commits
main
...
Dev-Anomal
| Author | SHA1 | Date | |
|---|---|---|---|
| 1020555db6 | |||
| 615cf282a1 | |||
| d040099b9a |
@@ -13,7 +13,6 @@ def insertar_alerta_en_db(cursor, tipo_alerta, id_entidad, entidad, mensaje, fec
|
|||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||||
"""
|
"""
|
||||||
try:
|
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
|
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_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
|
c_dev = int(cant_devuelta) if cant_devuelta is not None else None
|
||||||
@@ -31,6 +30,9 @@ DB_DATABASE = 'SistemaGestion'
|
|||||||
CONNECTION_STRING = f'DRIVER={{ODBC Driver 18 for SQL Server}};SERVER={DB_SERVER};DATABASE={DB_DATABASE};Trusted_Connection=yes;TrustServerCertificate=yes;'
|
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_INDIVIDUAL_FILE = 'modelo_anomalias.joblib'
|
||||||
MODEL_SISTEMA_FILE = 'modelo_sistema_anomalias.joblib'
|
MODEL_SISTEMA_FILE = 'modelo_sistema_anomalias.joblib'
|
||||||
|
MODEL_DIST_FILE = 'modelo_anomalias_dist.joblib'
|
||||||
|
MODEL_DANADAS_FILE = 'modelo_danadas.joblib'
|
||||||
|
MODEL_MONTOS_FILE = 'modelo_montos.joblib'
|
||||||
|
|
||||||
# --- 2. Determinar Fecha ---
|
# --- 2. Determinar Fecha ---
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
@@ -46,11 +48,12 @@ except Exception as e:
|
|||||||
print(f"CRITICAL: No se pudo conectar a la base de datos. Error: {e}")
|
print(f"CRITICAL: No se pudo conectar a la base de datos. Error: {e}")
|
||||||
exit()
|
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) ---")
|
print("\n--- FASE 1: Detección de Anomalías Individuales (Canillitas) ---")
|
||||||
if not os.path.exists(MODEL_INDIVIDUAL_FILE):
|
if not os.path.exists(MODEL_INDIVIDUAL_FILE):
|
||||||
print(f"ADVERTENCIA: Modelo individual '{MODEL_INDIVIDUAL_FILE}' no encontrado.")
|
print(f"ADVERTENCIA: Modelo individual '{MODEL_INDIVIDUAL_FILE}' no encontrado.")
|
||||||
else:
|
else:
|
||||||
|
# ... (esta sección se mantiene exactamente igual que antes) ...
|
||||||
model_individual = joblib.load(MODEL_INDIVIDUAL_FILE)
|
model_individual = joblib.load(MODEL_INDIVIDUAL_FILE)
|
||||||
query_individual = f"""
|
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
|
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 +84,16 @@ else:
|
|||||||
cant_devuelta=row['cantidad_devuelta'],
|
cant_devuelta=row['cantidad_devuelta'],
|
||||||
porc_devolucion=row['porcentaje_devolucion'])
|
porc_devolucion=row['porcentaje_devolucion'])
|
||||||
else:
|
else:
|
||||||
print("INFO: No se encontraron anomalías individuales significativas.")
|
print("INFO: No se encontraron anomalías individuales significativas en canillitas.")
|
||||||
else:
|
else:
|
||||||
print("INFO: No hay datos de canillitas para analizar en la fecha seleccionada.")
|
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 ---")
|
print("\n--- FASE 2: Detección de Anomalías de Sistema ---")
|
||||||
if not os.path.exists(MODEL_SISTEMA_FILE):
|
if not os.path.exists(MODEL_SISTEMA_FILE):
|
||||||
print(f"ADVERTENCIA: Modelo de sistema '{MODEL_SISTEMA_FILE}' no encontrado.")
|
print(f"ADVERTENCIA: Modelo de sistema '{MODEL_SISTEMA_FILE}' no encontrado.")
|
||||||
else:
|
else:
|
||||||
|
# ... (esta sección se mantiene exactamente igual que antes) ...
|
||||||
model_sistema = joblib.load(MODEL_SISTEMA_FILE)
|
model_sistema = joblib.load(MODEL_SISTEMA_FILE)
|
||||||
query_agregada = f"""
|
query_agregada = f"""
|
||||||
SELECT CAST(Fecha AS DATE) AS fecha_dia, DATEPART(weekday, Fecha) as dia_semana,
|
SELECT CAST(Fecha AS DATE) AS fecha_dia, DATEPART(weekday, Fecha) as dia_semana,
|
||||||
@@ -128,7 +132,161 @@ else:
|
|||||||
mensaje=mensaje,
|
mensaje=mensaje,
|
||||||
fecha_anomalia=target_date.date())
|
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.")
|
||||||
|
|
||||||
|
# --- FASE 4: Detección de Anomalías en Bobinas Dañadas ---
|
||||||
|
print("\n--- FASE 4: Detección de Anomalías en Bobinas Dañadas ---")
|
||||||
|
if not os.path.exists(MODEL_DANADAS_FILE):
|
||||||
|
print(f"ADVERTENCIA: Modelo de bobinas dañadas '{MODEL_DANADAS_FILE}' no encontrado.")
|
||||||
|
else:
|
||||||
|
model_danadas = joblib.load(MODEL_DANADAS_FILE)
|
||||||
|
query_danadas = f"""
|
||||||
|
SELECT
|
||||||
|
h.Id_Planta as id_planta,
|
||||||
|
p.Nombre as nombre_planta,
|
||||||
|
DATEPART(weekday, h.FechaMod) as dia_semana,
|
||||||
|
COUNT(DISTINCT h.Id_Bobina) as cantidad_danadas
|
||||||
|
FROM
|
||||||
|
bob_StockBobinas_H h
|
||||||
|
JOIN
|
||||||
|
bob_dtPlantas p ON h.Id_Planta = p.Id_Planta
|
||||||
|
WHERE
|
||||||
|
h.Id_EstadoBobina = 3 -- Asumiendo ID 3 para 'Dañada'
|
||||||
|
AND h.TipoMod = 'Estado: Dañada'
|
||||||
|
AND CAST(h.FechaMod AS DATE) = '{target_date.strftime('%Y-%m-%d')}'
|
||||||
|
GROUP BY
|
||||||
|
h.Id_Planta, p.Nombre, DATEPART(weekday, h.FechaMod)
|
||||||
|
"""
|
||||||
|
df_danadas_new = pd.read_sql(query_danadas, cnxn)
|
||||||
|
|
||||||
|
if not df_danadas_new.empty:
|
||||||
|
for index, row in df_danadas_new.iterrows():
|
||||||
|
features_danadas = ['id_planta', 'dia_semana', 'cantidad_danadas']
|
||||||
|
X_danadas_new = row[features_danadas].to_frame().T
|
||||||
|
|
||||||
|
prediction = model_danadas.predict(X_danadas_new)
|
||||||
|
|
||||||
|
if prediction[0] == -1:
|
||||||
|
mensaje = f"Se registraron {row['cantidad_danadas']} bobina(s) dañada(s) en la Planta '{row['nombre_planta']}', un valor inusualmente alto."
|
||||||
|
insertar_alerta_en_db(cursor,
|
||||||
|
tipo_alerta='ExcesoBobinasDañadas',
|
||||||
|
id_entidad=row['id_planta'],
|
||||||
|
entidad='Planta',
|
||||||
|
mensaje=mensaje,
|
||||||
|
fecha_anomalia=target_date.date())
|
||||||
|
print(f"INFO: Análisis de {len(df_danadas_new)} planta(s) con bobinas dañadas completado.")
|
||||||
|
else:
|
||||||
|
print("INFO: No se registraron bobinas dañadas en la fecha seleccionada.")
|
||||||
|
|
||||||
|
# --- FASE 5: Detección de Anomalías en Montos Contables ---
|
||||||
|
print("\n--- FASE 5: Detección de Anomalías en Montos Contables ---")
|
||||||
|
if not os.path.exists(MODEL_MONTOS_FILE):
|
||||||
|
print(f"ADVERTENCIA: Modelo de montos contables '{MODEL_MONTOS_FILE}' no encontrado.")
|
||||||
|
else:
|
||||||
|
model_montos = joblib.load(MODEL_MONTOS_FILE)
|
||||||
|
|
||||||
|
# Consulta unificada para obtener todas las transacciones del día
|
||||||
|
query_transacciones = f"""
|
||||||
|
SELECT 'Distribuidor' AS entidad, p.Id_Distribuidor AS id_entidad, d.Nombre as nombre_entidad, p.Id_Empresa as id_empresa, p.Fecha as fecha, p.TipoMovimiento as tipo_transaccion, p.Monto as monto
|
||||||
|
FROM cue_PagosDistribuidor p JOIN dist_dtDistribuidores d ON p.Id_Distribuidor = d.Id_Distribuidor
|
||||||
|
WHERE CAST(p.Fecha AS DATE) = '{target_date.strftime('%Y-%m-%d')}'
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
CASE WHEN cd.Destino = 'Distribuidores' THEN 'Distribuidor' ELSE 'Canillita' END AS entidad,
|
||||||
|
cd.Id_Destino AS id_entidad,
|
||||||
|
COALESCE(d.Nombre, c.NomApe) as nombre_entidad,
|
||||||
|
cd.Id_Empresa as id_empresa,
|
||||||
|
cd.Fecha as fecha,
|
||||||
|
cd.Tipo as tipo_transaccion,
|
||||||
|
cd.Monto as monto
|
||||||
|
FROM cue_CreditosDebitos cd
|
||||||
|
LEFT JOIN dist_dtDistribuidores d ON cd.Id_Destino = d.Id_Distribuidor AND cd.Destino = 'Distribuidores'
|
||||||
|
LEFT JOIN dist_dtCanillas c ON cd.Id_Destino = c.Id_Canilla AND cd.Destino = 'Canillas'
|
||||||
|
WHERE CAST(cd.Fecha AS DATE) = '{target_date.strftime('%Y-%m-%d')}'
|
||||||
|
"""
|
||||||
|
|
||||||
|
df_transacciones_new = pd.read_sql(query_transacciones, cnxn)
|
||||||
|
|
||||||
|
if not df_transacciones_new.empty:
|
||||||
|
# Aplicar exactamente el mismo pre-procesamiento que en el entrenamiento
|
||||||
|
df_transacciones_new['tipo_transaccion_cat'] = pd.Categorical(df_transacciones_new['tipo_transaccion']).codes
|
||||||
|
df_transacciones_new['dia_semana'] = pd.to_datetime(df_transacciones_new['fecha']).dt.dayofweek
|
||||||
|
|
||||||
|
features = ['id_entidad', 'id_empresa', 'tipo_transaccion_cat', 'dia_semana', 'monto']
|
||||||
|
X_new = df_transacciones_new[features]
|
||||||
|
|
||||||
|
df_transacciones_new['anomalia'] = model_montos.predict(X_new)
|
||||||
|
anomalias_detectadas = df_transacciones_new[df_transacciones_new['anomalia'] == -1]
|
||||||
|
|
||||||
|
if not anomalias_detectadas.empty:
|
||||||
|
for index, row in anomalias_detectadas.iterrows():
|
||||||
|
tipo_alerta = 'MontoInusualPago' if row['tipo_transaccion'] in ['Recibido', 'Realizado'] else 'MontoInusualNota'
|
||||||
|
mensaje = f"Se registró un '{row['tipo_transaccion']}' de ${row['monto']:,} para '{row['nombre_entidad']}', un valor atípico."
|
||||||
|
|
||||||
|
insertar_alerta_en_db(cursor,
|
||||||
|
tipo_alerta=tipo_alerta,
|
||||||
|
id_entidad=row['id_entidad'],
|
||||||
|
entidad=row['entidad'],
|
||||||
|
mensaje=mensaje,
|
||||||
|
fecha_anomalia=row['fecha'].date())
|
||||||
|
else:
|
||||||
|
print("INFO: No se encontraron anomalías en los montos contables registrados.")
|
||||||
|
else:
|
||||||
|
print("INFO: No hay transacciones contables para analizar en la fecha seleccionada.")
|
||||||
|
|
||||||
|
# --- Finalización ---
|
||||||
cnxn.commit()
|
cnxn.commit()
|
||||||
cnxn.close()
|
cnxn.close()
|
||||||
print("\n--- DETECCIÓN COMPLETA ---")
|
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.
BIN
ProyectoIA_Gestion/modelo_danadas.joblib
Normal file
BIN
ProyectoIA_Gestion/modelo_danadas.joblib
Normal file
Binary file not shown.
BIN
ProyectoIA_Gestion/modelo_montos.joblib
Normal file
BIN
ProyectoIA_Gestion/modelo_montos.joblib
Normal file
Binary file not shown.
63
ProyectoIA_Gestion/train_danadas.py
Normal file
63
ProyectoIA_Gestion/train_danadas.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from sklearn.ensemble import IsolationForest
|
||||||
|
import joblib
|
||||||
|
import pyodbc
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
print("--- INICIANDO SCRIPT DE ENTRENAMIENTO (BOBINAS DAÑADAS) ---")
|
||||||
|
|
||||||
|
# --- 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_danadas.joblib'
|
||||||
|
CONTAMINATION_RATE = 0.02 # Un 2% de los días podrían tener una cantidad anómala de bobinas dañadas (ajustable)
|
||||||
|
|
||||||
|
# --- 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=730)
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
CAST(h.FechaMod AS DATE) as fecha,
|
||||||
|
DATEPART(weekday, h.FechaMod) as dia_semana,
|
||||||
|
h.Id_Planta as id_planta,
|
||||||
|
COUNT(DISTINCT h.Id_Bobina) as cantidad_danadas
|
||||||
|
FROM
|
||||||
|
bob_StockBobinas_H h
|
||||||
|
WHERE
|
||||||
|
h.Id_EstadoBobina = 3 -- 3 es el ID del estado 'Dañada'
|
||||||
|
AND h.FechaMod >= '{fecha_limite.strftime('%Y-%m-%d')}'
|
||||||
|
GROUP BY
|
||||||
|
CAST(h.FechaMod AS DATE),
|
||||||
|
DATEPART(weekday, h.FechaMod),
|
||||||
|
h.Id_Planta
|
||||||
|
"""
|
||||||
|
print("Ejecutando consulta para obtener historial de bobinas dañadas...")
|
||||||
|
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 bobinas dañadas en el último año. Saliendo.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# --- 3. Preparación de Datos ---
|
||||||
|
print(f"Preparando {len(df)} registros agregados para el entrenamiento...")
|
||||||
|
# Las características serán la planta, el día de la semana y la cantidad de bobinas dañadas ese día
|
||||||
|
features = ['id_planta', 'dia_semana', 'cantidad_danadas']
|
||||||
|
X = df[features]
|
||||||
|
|
||||||
|
# --- 4. Entrenamiento y Guardado ---
|
||||||
|
print(f"Entrenando el modelo de bobinas dañadas 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 BOBINAS DAÑADAS COMPLETADO. Modelo guardado en '{MODEL_FILE}' ---")
|
||||||
68
ProyectoIA_Gestion/train_distribuidores.py
Normal file
68
ProyectoIA_Gestion/train_distribuidores.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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'
|
||||||
|
CONTAMINATION_RATE = 0.01 # Un 1% de contaminación
|
||||||
|
|
||||||
|
# --- 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=730)
|
||||||
|
|
||||||
|
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']
|
||||||
|
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}' ---")
|
||||||
92
ProyectoIA_Gestion/train_montos.py
Normal file
92
ProyectoIA_Gestion/train_montos.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from sklearn.ensemble import IsolationForest
|
||||||
|
import joblib
|
||||||
|
import pyodbc
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
print("--- INICIANDO SCRIPT DE ENTRENAMIENTO (MONTOS CONTABLES) ---")
|
||||||
|
|
||||||
|
# --- 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_montos.joblib'
|
||||||
|
CONTAMINATION_RATE = 0.002
|
||||||
|
|
||||||
|
# --- 2. Carga de Datos de Múltiples Tablas ---
|
||||||
|
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=730) # Usamos 2 años de datos para tener más contexto financiero
|
||||||
|
|
||||||
|
# Query para Pagos a Distribuidores
|
||||||
|
query_pagos = f"""
|
||||||
|
SELECT
|
||||||
|
'Distribuidor' AS entidad,
|
||||||
|
Id_Distribuidor AS id_entidad,
|
||||||
|
Id_Empresa AS id_empresa,
|
||||||
|
Fecha AS fecha,
|
||||||
|
TipoMovimiento AS tipo_transaccion,
|
||||||
|
Monto AS monto
|
||||||
|
FROM
|
||||||
|
cue_PagosDistribuidor
|
||||||
|
WHERE
|
||||||
|
Fecha >= '{fecha_limite.strftime('%Y-%m-%d')}'
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Query para Notas de Crédito/Débito
|
||||||
|
query_notas = f"""
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN Destino = 'Distribuidores' THEN 'Distribuidor'
|
||||||
|
WHEN Destino = 'Canillas' THEN 'Canillita'
|
||||||
|
ELSE 'Desconocido'
|
||||||
|
END AS entidad,
|
||||||
|
Id_Destino AS id_entidad,
|
||||||
|
Id_Empresa AS id_empresa,
|
||||||
|
Fecha AS fecha,
|
||||||
|
Tipo AS tipo_transaccion,
|
||||||
|
Monto AS monto
|
||||||
|
FROM
|
||||||
|
cue_CreditosDebitos
|
||||||
|
WHERE
|
||||||
|
Fecha >= '{fecha_limite.strftime('%Y-%m-%d')}'
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Ejecutando consultas para obtener datos de pagos y notas...")
|
||||||
|
df_pagos = pd.read_sql(query_pagos, cnxn)
|
||||||
|
df_notas = pd.read_sql(query_notas, cnxn)
|
||||||
|
|
||||||
|
cnxn.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error al conectar o consultar la base de datos: {e}")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# --- 3. Unificación y Preparación de Datos ---
|
||||||
|
if df_pagos.empty and df_notas.empty:
|
||||||
|
print("No se encontraron datos de entrenamiento en el período seleccionado. Saliendo.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Combinamos ambos dataframes
|
||||||
|
df = pd.concat([df_pagos, df_notas], ignore_index=True)
|
||||||
|
print(f"Preparando {len(df)} registros contables para el entrenamiento...")
|
||||||
|
|
||||||
|
# Feature Engineering: Convertir textos a números categóricos
|
||||||
|
# Esto ayuda al modelo a entender "Recibido", "Credito", etc., como categorías distintas.
|
||||||
|
df['tipo_transaccion_cat'] = pd.Categorical(df['tipo_transaccion']).codes
|
||||||
|
df['dia_semana'] = df['fecha'].dt.dayofweek
|
||||||
|
|
||||||
|
# Las características para el modelo serán el contexto de la transacción y su monto
|
||||||
|
features = ['id_entidad', 'id_empresa', 'tipo_transaccion_cat', 'dia_semana', 'monto']
|
||||||
|
X = df[features]
|
||||||
|
|
||||||
|
# --- 4. Entrenamiento y Guardado ---
|
||||||
|
print(f"Entrenando el modelo de montos contables 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 MONTOS COMPLETADO. Modelo guardado en '{MODEL_FILE}' ---")
|
||||||
Reference in New Issue
Block a user