Ruptura_Projetada/relatório_ruptura/ruptura projetada 23.05.py
João Herculano b2fb8fd8e3 innit 29.05
2025-05-29 09:48:20 -03:00

221 lines
8.0 KiB
Python

import smtplib
import ssl
import psycopg2
import configparser
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from email.message import EmailMessage
from email.utils import make_msgid
from pathlib import Path
from datetime import datetime, time
from email.mime.image import MIMEImage
config = configparser.ConfigParser()
config.read(r"C:\Users\joao.herculano\Documents\Enviador de email\credenciais.ini")
conn = psycopg2.connect(
host=config['banco']['host'],
port="5432",
database="ginseng",
user=config['banco']['user'],
password=config['banco']['password']
)
calendario = pd.read_excel(r"C:\Users\joao.herculano\GRUPO GINSENG\Assistência Suprimentos - 2025\SUPRIMENTOS\BD_LANÇAMENTOS\BASE DE DADOS LANÇAMENTO\BOT\CICLO 9\CALENDARIO_CICLO\Ciclo_Expandido_com_Datas.xlsx")
calendario.columns = calendario.columns.str.lower()
calendario['date'] = pd.to_datetime(calendario['date'])
today = pd.Timestamp("today").normalize()
calendario = calendario[calendario['marca'] == "BOTICARIO"]
calendario['num_ciclo'] = calendario['ciclo'].str[-2:].astype(int)
calendario['ano_ciclo'] = calendario['ciclo'].str[0:5]
calendario['ciclomais2'] = calendario['ano_ciclo'].astype(str) + (calendario['num_ciclo'] + 0).astype(str).str.zfill(2)
ciclo_mais2 = calendario[calendario['date'].dt.normalize() == today]['ciclomais2'].iloc[0]
filtered_calendario = calendario[calendario['ciclo'] == ciclo_mais2][:1].copy()
filtered_calendario['dias_ate_fim'] = (filtered_calendario['fim ciclo'].iloc[0] - today).days
print(filtered_calendario[['duração', 'dias_ate_fim']])
query = '''
SELECT
businessunit AS marca,
codcategory AS categoria,
loja_id AS pdv,
code AS sku,
description AS descricao_produto,
salescurve AS curva,
CASE WHEN promotions_description = '' THEN 'REGULAR' ELSE 'PROMOÇÃO' END AS tipo_promocao,
COALESCE(stock_actual, 0) AS estoque,
stock_intransit AS transito,
nextcycleprojection AS pv_mar,
currentcyclesales AS venda_atual,
CASE WHEN criticalitem_iscritical = false THEN 'REGULAR' ELSE 'CRITICO' END AS status_item
FROM Draft
WHERE isproductdeactivated = false AND codcategory NOT IN ('SUPORTE A VENDA','EMBALAGENS')
'''
df = pd.read_sql(query, conn)
conn.close()
df.columns = df.columns.str.lower()
filtered_calendario.columns = filtered_calendario.columns.str.lower()
df['ddv'] = df['pv_mar'] / filtered_calendario['duração'].values[0]
df['estoque_seguranca'] = np.ceil(df['pv_mar'] + (15 * df['ddv'])).astype(int)
df['risco_ruptura'] = np.where(df['ddv'] * filtered_calendario['dias_ate_fim'].max() >= df['estoque'], "SIM", "NÃO")
df['quantidade_ruptura'] = np.ceil(df['ddv'] * filtered_calendario['dias_ate_fim'].max() - df['estoque'])
df['excesso'] = np.where(df['estoque'] - df['estoque_seguranca'] > 0, df['estoque'] - df['estoque_seguranca'], 0)
remetente = config['credenciais']['remetente']
senha = config['credenciais']['senha']
destinatarios = [email.strip() for email in config['email_ruptura']['destinatarios'].split(',')]
assunto = config['email_ruptura']['assunto']
df_rpt = pd.read_excel(r"C:\Users\joao.herculano\Downloads\Ruptura Cliente CP GINSENG (1).xlsx")
df_rpt.columns = df_rpt.columns.str.lower()
df['pdv'] = df['pdv'].astype('Int64')
df['sku'] = df['sku'].astype('Int64')
df_rpt['cod_pdv'] = df_rpt['cod_pdv'].astype('Int64')
df_rpt['sku1'] = df_rpt['sku1'].astype('Int64')
df = pd.merge(df, df_rpt[['sku1','cod_pdv','estoque livre?']], left_on=['pdv','sku'], right_on=['cod_pdv','sku1'], how='left')
df.drop(columns=['cod_pdv'], inplace=True)
pdvs = pd.read_excel(r"C:\Users\joao.herculano\Documents\PDV_ATT.xlsx")
pdvs.columns = pdvs.columns.str.lower()
df['pdv'] = df['pdv'].astype('Int64')
pdvs['pdv'] = pdvs['pdv'].astype('Int64')
df2 = pd.merge(df, pdvs[['pdv','uf','canal','analista']], on='pdv', how='inner')
idx = df2.groupby(['uf', 'sku'])['excesso'].idxmax()
pdvs_maior_excesso = df2.loc[idx, ['uf', 'sku', 'pdv', 'excesso']].copy()
pdvs_maior_excesso.columns = ['uf', 'sku', 'pdv_maior_excesso', 'maior_excesso_por_uf']
pdvs_maior_excesso.set_index(['uf', 'sku'], inplace=True)
df2 = df2.join(pdvs_maior_excesso, on=['uf', 'sku'])
df2['maior excesso na uf'] = df2['maior_excesso_por_uf'].apply(lambda x: 'não tem excesso no uf' if x == 0 else None)
df2['maior excesso na uf'] = df2['maior excesso na uf'].combine_first(df2['pdv_maior_excesso'])
df2['quantidade_ruptura'] = df2['quantidade_ruptura'].clip(lower=0)
print(df2[['uf', 'sku', 'excesso', 'maior_excesso_por_uf', 'maior excesso na uf']].head())
df2.drop(columns=['pdv_maior_excesso','sku1'], inplace=True)
df2['estoque livre?'] = np.where(
df2['estoque livre?'].isna() & (df2['status_item'] == 'CRITICO'),
'Não',
df2['estoque livre?']
)
df2['estoque livre?'] = np.where(
df2['estoque livre?'].isna() & (df2['status_item'] == 'REGULAR'),
'Sim',
df2['estoque livre?']
)
excel_path = Path("relatorio.xlsx")
colunas_ordenadas = [
'uf',
'canal',
'analista',
'marca',
'categoria',
'pdv',
'sku',
'descricao_produto',
'curva',
'tipo_promocao',
'estoque',
'transito',
'pv_mar',
'venda_atual',
'status_item',
'ddv',
'estoque_seguranca',
'risco_ruptura',
'quantidade_ruptura',
'excesso',
'estoque livre?',
'maior_excesso_por_uf',
'maior excesso na uf'
]
df2 = df2[colunas_ordenadas]
df3 = df2[df2['canal'] != "LJ"]
df3 = df3.groupby(['uf', 'canal','pdv'])['quantidade_ruptura'].sum().sort_values(ascending=False).reset_index()
with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
df2.to_excel(writer, sheet_name='Detalhado', index=False)
df3.to_excel(writer, sheet_name='Resumo', index=False)
ruptura_total = df2['quantidade_ruptura'].sum()
ruptura_por_uf_pct = (
df2.groupby('uf')['quantidade_ruptura'].sum()
.sort_values(ascending=True)
.apply(lambda x: (x / ruptura_total) * 100)
)
print(ruptura_por_uf_pct)
ax = ruptura_por_uf_pct.plot(kind='barh', figsize=(10, 6), color='skyblue')
for i, v in enumerate(ruptura_por_uf_pct):
ax.text(v + 0.3, i, f"{v:.1f}%", va='center')
plt.xlabel('% da ruptura total')
plt.title('Distribuição percentual de ruptura projetada por UF')
plt.tight_layout()
plt.savefig("grafico.png")
plt.close()
agora = datetime.now().time()
if time(5, 0) <= agora <= time(12, 0):
boa = "Bom dia!"
elif time(12, 1) <= agora <= time(18, 0):
boa = "Boa tarde!"
else:
boa = "Boa noite!"
grafico_cid = make_msgid()[1:-1]
msg = EmailMessage()
msg['From'] = remetente
msg['To'] = ', '.join(destinatarios)
msg['Subject'] = assunto
html_email = f"""
<html>
<body>
<p>{boa}</p>
<p>Compartilhamos o relatório de ruptura projetada, com o objetivo de monitorar os itens com maior risco de ruptura nos próximos dias.</p>
<p>O relatório a seguir apresenta as seguintes informações:</p>
<ul>
<li>Estoque atual;</li>
<li>Trânsito;</li>
<li>Ruptura projetada;</li>
<li>Maior excesso por estado, assim como o PDV que o possui.</li>
</ul>
<p>Além disso, o material destaca as VDs com maior criticidade, permitindo uma atuação direcionada para mitigar impactos e priorizar ações de abastecimento e transferência.</p>
<p><strong>Importante:</strong> O relatório está em processo de desenvolvimento e pode sofrer mudanças futuras no layout. Ficamos à disposição para esclarecer quaisquer dúvidas.</p>
<img src="cid:{grafico_cid}">
</body>
</html>
"""
msg.set_content("Seu e-mail precisa de um visualizador HTML.")
msg.add_alternative(html_email, subtype='html')
with open("grafico.png", 'rb') as img:
msg.get_payload()[1].add_related(img.read(), 'image', 'png', cid=grafico_cid)
with open(excel_path, 'rb') as f:
msg.add_attachment(
f.read(),
maintype='application',
subtype='vnd.openxmlformats-officedocument.spreadsheetml.sheet',
filename=excel_path.name
)
with smtplib.SMTP('smtp-mail.outlook.com', 587) as smtp:
smtp.ehlo()
smtp.starttls(context=ssl.create_default_context())
smtp.login(remetente, senha)
smtp.send_message(msg)
print("E-mail enviado com sucesso.")