140 lines
5.2 KiB
Python
140 lines
5.2 KiB
Python
"""
|
|
generar_lista.py
|
|
Genera datos.js con predicciones de compra limpias a partir de lista_compra_estimado.csv
|
|
Uso: python generar_lista.py
|
|
"""
|
|
import pandas as pd
|
|
import json
|
|
import re
|
|
import os
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
# --- Argumentos --------------------------------------------------------
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--usuario', default='default', help='Nombre del usuario')
|
|
args = parser.parse_args()
|
|
|
|
input_csv = os.path.join('datos', args.usuario, 'lista_compra_estimado.csv')
|
|
output_dir = os.path.join('datos', args.usuario)
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Filtros de basura (líneas del ticket que no son productos)
|
|
# ---------------------------------------------------------------------------
|
|
BAD_PATTERNS = [
|
|
r'^\s*%',
|
|
r'tarjeta',
|
|
r'^total(\s|$|\s*\()',
|
|
r'€/kg',
|
|
r'\d+,\d+\s*kg',
|
|
r'^n\.c',
|
|
r'^aut[\s\d]',
|
|
r'verificado',
|
|
r'visa',
|
|
r'bancaria',
|
|
r'devoluciones',
|
|
r'mastercard',
|
|
r'importe',
|
|
r'^iva\s',
|
|
r'^cuota\s',
|
|
r'^\d+,\d+\s*€', # líneas de precio suelto
|
|
r'^\d{1,3}\s*€', # líneas tipo "15 €"
|
|
]
|
|
|
|
def es_basura(nombre):
|
|
n = nombre.lower().strip()
|
|
if not n or len(n) < 3:
|
|
return True
|
|
return any(re.search(p, n) for p in BAD_PATTERNS)
|
|
|
|
def limpiar_nombre(nombre):
|
|
nombre = nombre.strip()
|
|
# Quitar el "1" pegado al inicio del nombre que viene de la cantidad en el ticket
|
|
# "1Yogur Limon" → "Yogur Limon" | "1+Prot Pud" → "+Prot Pud"
|
|
# Pero "16 Huevos Camperos" → "16 Huevos Camperos" (es un pack de 16 huevos)
|
|
nombre = re.sub(r'^1([A-ZÁÉÍÓÚÑ+a-záéíóúñ])', r'\1', nombre)
|
|
return nombre.strip()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cargar datos
|
|
# ---------------------------------------------------------------------------
|
|
try:
|
|
# Intentar UTF-8 primero, si falla usar cp1252 (Windows)
|
|
try:
|
|
df = pd.read_csv(input_csv, encoding='utf-8')
|
|
except UnicodeDecodeError:
|
|
df = pd.read_csv(input_csv, encoding='cp1252')
|
|
except FileNotFoundError:
|
|
print("❌ No se encontró", input_csv)
|
|
print(" Ejecuta primero: python autocompra7.py --usuario", args.usuario)
|
|
exit(1)
|
|
|
|
print(f" Productos en CSV: {len(df)}")
|
|
|
|
if df.empty:
|
|
print("[info] CSV vacio, generando datos.json vacio.")
|
|
datos_json = {'generado': datetime.now().strftime('%d/%m/%Y %H:%M'), 'total': 0, 'predicciones': []}
|
|
with open(os.path.join(output_dir, 'datos.json'), 'w', encoding='utf-8') as f:
|
|
json.dump(datos_json, f, ensure_ascii=False, indent=2)
|
|
exit(0)
|
|
|
|
# Filtrar basura
|
|
df = df[~df['producto'].apply(es_basura)].copy()
|
|
print(f" Después de filtrar basura: {len(df)}")
|
|
|
|
# Limpiar nombres
|
|
df['nombre'] = df['producto'].apply(limpiar_nombre)
|
|
|
|
# Parsear fechas estimadas
|
|
df['fecha'] = pd.to_datetime(df['fecha_estimada_proxima_compra'], format='%d/%m/%Y', errors='coerce')
|
|
df = df.dropna(subset=['fecha', 'diferencia_dias'])
|
|
# Excluir productos sin frecuencia real (frecuencia = 0 significa comprado una sola vez)
|
|
df = df[df['diferencia_dias'] > 0]
|
|
|
|
# Eliminar duplicados (mismo nombre → quedarse con el de menor frecuencia, más fiable)
|
|
df = df.sort_values('diferencia_dias')
|
|
df = df.drop_duplicates(subset='nombre', keep='first')
|
|
|
|
# Calcular días hasta la próxima compra desde HOY
|
|
hoy = pd.Timestamp(datetime.now().date())
|
|
df['dias_hasta'] = (df['fecha'] - hoy).dt.days
|
|
|
|
# Ordenar: primero los más frecuentes (más urgentes a recoger), luego por fecha estimada
|
|
df = df.sort_values(['diferencia_dias', 'dias_hasta'])
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Construir JSON de salida
|
|
# ---------------------------------------------------------------------------
|
|
resultado = []
|
|
for _, row in df.iterrows():
|
|
resultado.append({
|
|
'producto': row['nombre'],
|
|
'fecha_estimada': row['fecha_estimada_proxima_compra'],
|
|
'dias_hasta': int(row['dias_hasta']),
|
|
'frecuencia_dias': round(float(row['diferencia_dias']), 1),
|
|
'es_estacional': bool(row['es_estacional']) if 'es_estacional' in row and pd.notna(row['es_estacional']) else False,
|
|
'meses_temporada': str(row['meses_temporada']) if 'meses_temporada' in row and pd.notna(row['meses_temporada']) else '',
|
|
})
|
|
|
|
# Escribir datos.js (cargable como <script src> sin servidor HTTP)
|
|
ts = datetime.now().strftime('%d/%m/%Y %H:%M')
|
|
with open(os.path.join(output_dir, 'datos.js'), 'w', encoding='utf-8') as f:
|
|
f.write('// Generado el ' + ts + ' - ' + str(len(resultado)) + ' productos\n')
|
|
f.write('const GENERADO = "' + ts + '";\n')
|
|
f.write('const predicciones = ')
|
|
json.dump(resultado, f, ensure_ascii=False, indent=2)
|
|
f.write(';\n')
|
|
|
|
# Escribir datos.json (usado por la app Flask)
|
|
datos_json = {
|
|
'generado': ts,
|
|
'total': len(resultado),
|
|
'predicciones': resultado,
|
|
}
|
|
with open(os.path.join(output_dir, 'datos.json'), 'w', encoding='utf-8') as f:
|
|
json.dump(datos_json, f, ensure_ascii=False, indent=2)
|
|
|
|
print(f"✓ {len(resultado)} predicciones escritas en datos.json ({args.usuario})")
|
|
print(f" Abre index.html en el navegador o arranca app.py para ver la lista.")
|