innit 29.05
This commit is contained in:
parent
c7f31231ae
commit
b2fb8fd8e3
1481
Lançamentos/Script_lançamento_EUD_v2.ipynb
Normal file
1481
Lançamentos/Script_lançamento_EUD_v2.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
785
Lançamentos/Script_lançamento_boti_v2.ipynb
Normal file
785
Lançamentos/Script_lançamento_boti_v2.ipynb
Normal file
@ -0,0 +1,785 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6ad35669",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np \n",
|
||||
"import glob\n",
|
||||
"import os "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9fcdc77a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"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\")\n",
|
||||
"\n",
|
||||
"calendario['Date'] = pd.to_datetime(calendario['Date'])\n",
|
||||
"\n",
|
||||
"# Get today (normalized to midnight)\n",
|
||||
"today = pd.Timestamp(\"today\").normalize()\n",
|
||||
"\n",
|
||||
"calendario['NUM_CICLO'] = calendario['Ciclo'].str[-2:].astype(int)\n",
|
||||
"\n",
|
||||
"calendario['ANO_CICLO'] = calendario['Ciclo'].str[0:5]\n",
|
||||
"\n",
|
||||
"calendario = calendario[calendario['MARCA'] == \"BOTICARIO\"]\n",
|
||||
"\n",
|
||||
"calendario['CICLOMAIS2'] = calendario['ANO_CICLO'].astype(str) + (calendario['NUM_CICLO'].astype(int) + 3).astype(str).str.zfill(2) #<<< MUDAR O \"4\" (CICLO ATUAL + 4 PARA ACHAR O CICLO DA SUGESTÃO) EX: C202505 -> C202509\n",
|
||||
"ciclo_mais2 = calendario[calendario['Date'].dt.normalize() == today]['CICLOMAIS2'].iloc[0]\n",
|
||||
"\n",
|
||||
"# Filter rows where date matches today\n",
|
||||
"filtered_calendario = calendario[calendario['Ciclo'] == ciclo_mais2][:1]\n",
|
||||
"\n",
|
||||
"filtered_calendario['dias_ate_inicio'] = filtered_calendario['INICIO CICLO'].iloc[0] - today\n",
|
||||
"\n",
|
||||
"filtered_calendario['dias_ate_inicio'] = filtered_calendario['dias_ate_inicio'].dt.days.astype(int)\n",
|
||||
"\n",
|
||||
"filtered_calendario['match'] = 1\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bbec229d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"filtered_calendario"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "61ffc777",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_similares = pd.read_excel(r\"C:\\Users\\joao.herculano\\GRUPO GINSENG\\Assistência Suprimentos - 2025\\SUPRIMENTOS\\BD_LANÇAMENTOS\\BOT\\BOT - C11\\arquivos para geração da sugestão\\SIMILARES\\PRODUTOS SIMILARES - BOT.xlsx\")\n",
|
||||
"\n",
|
||||
"df_similares = pd.merge(left=df_similares,right=calendario[['Ciclo','INICIO CICLO','FIM CICLO','DURAÇÃO']], how= 'left', left_on = 'CICLO SIMILAR',right_on = 'Ciclo' )\n",
|
||||
"\n",
|
||||
"df_similares = df_similares.drop(columns=['Ciclo'])\n",
|
||||
"\n",
|
||||
"df_similares = df_similares.rename(columns={'INICIO CICLO':'INICIO CICLO SIMILAR','FIM CICLO':'FIM CICLO SIMILAR','DURAÇÃO':'DURAÇÃO CICLO SIMILAR'})\n",
|
||||
"df_similares.drop_duplicates(inplace=True)\n",
|
||||
"\n",
|
||||
"df_similares['MATCH'] = 1\n",
|
||||
"\n",
|
||||
"df_similares = df_similares.drop(columns=['INICIO DO CICLO',\n",
|
||||
" 'FIM DO CICLO', 'DURAÇÃO CICLO','INICIO CICLO SIMILAR','FIM CICLO SIMILAR','DURAÇÃO CICLO SIMILAR'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "99ea95e6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_similares.columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fe922f62",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_tabela = pd.read_excel(r\"C:\\Users\\joao.herculano\\GRUPO GINSENG\\Assistência Suprimentos - 2025\\SUPRIMENTOS\\BD_LANÇAMENTOS\\BOT\\BOT - C11\\arquivos para geração da sugestão\\TABELA DE PEDIDO\\Pedidos Semanais Especiais - BOT - 202511.xlsx\")\n",
|
||||
"\n",
|
||||
"df_tabela = df_tabela[df_tabela['Região'] == 'NNE'] \n",
|
||||
"\n",
|
||||
"df_tabela = df_tabela[(df_tabela['Canal'] != 'Ecomm') | (df_tabela['Canal'] != 'Ecomm | VD') | (df_tabela['Canal'] != 'Ecomm | Loja')] \n",
|
||||
"\n",
|
||||
"df_tabela['Canal'] = np.where((df_tabela['Canal'] == \"Loja\") | (df_tabela['Canal'] == \"Todos\") | (df_tabela['Canal'] == \"Loja | VD\"),\"TODOS\",\"VD\")\n",
|
||||
"\n",
|
||||
"df_tabela = df_tabela[df_tabela['Tipo de promoção'].str.contains('Lançamentos', na=False)]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a3a045d9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_pdv = pd.read_excel(r\"C:\\Users\\joao.herculano\\GRUPO GINSENG\\Assistência Suprimentos - 2025\\SUPRIMENTOS\\BD_LANÇAMENTOS\\BASE DE DADOS LANÇAMENTO\\BOT\\PDV\\PDV_ATT.xlsx\")\n",
|
||||
"\n",
|
||||
"df_pdv_origi = pd.read_excel(r\"C:\\Users\\joao.herculano\\GRUPO GINSENG\\Assistência Suprimentos - 2025\\SUPRIMENTOS\\BD_LANÇAMENTOS\\BASE DE DADOS LANÇAMENTO\\BOT\\PDV\\PDV_ATT.xlsx\")\n",
|
||||
"\n",
|
||||
"df_pdv = df_pdv.rename(columns={'DESCRIÇÃO':'DESCRIÇÃO PDV'})\n",
|
||||
"\n",
|
||||
"df_pdv = df_pdv.drop(columns=['REGIÃO', 'ESTADO','CIDADE','GESTÃO','MARCA'])\n",
|
||||
"\n",
|
||||
"df_pdv['PDV'] = df_pdv['PDV DESC'].str.split(\"-\").str[0].str.strip()\n",
|
||||
"\n",
|
||||
"df_pdv['UF'] = np.where(df_pdv['UF'] == 'VDC','BA',df_pdv['UF'])\n",
|
||||
"\n",
|
||||
"#ignorando a PDV que ainda não está online\n",
|
||||
"df_pdv = df_pdv[df_pdv['DESCRIÇÃO PDV'] != '23813-COMERCIO-HIB VALENTE']\n",
|
||||
"\n",
|
||||
"df_pdv = df_pdv[df_pdv['status'] != \"INATIVO\"]\n",
|
||||
"\n",
|
||||
"df_pdv = df_pdv[df_pdv['status'] != \"MATRIZ\"]\n",
|
||||
"\n",
|
||||
"df_pdv = df_pdv[df_pdv['SUPERVISOR'] != 'Inativa']\n",
|
||||
"\n",
|
||||
"df_pdv['MATCH'] = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "849d5297",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_pdv.columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "df04a501",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_similares = pd.merge(left=df_similares,right=df_pdv,right_on=['MATCH'],left_on=['MATCH'],how='inner')\n",
|
||||
"\n",
|
||||
"df_similares = df_similares.drop_duplicates()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0da911af",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Caminho onde estão as subpastas com os arquivos CSV\n",
|
||||
"\n",
|
||||
"# Set the path to the folder containing CSV files\n",
|
||||
"folder_path = r\"C:\\Users\\joao.herculano\\GRUPO GINSENG\\Assistência Suprimentos - 2025\\SUPRIMENTOS\\BD_LANÇAMENTOS\\BOT\\BOT - C11\\arquivos para geração da sugestão\\DRAFT\" # arquivo dos drafts\n",
|
||||
"\n",
|
||||
"# Pattern to match all CSV files\n",
|
||||
"csv_files = glob.glob(os.path.join(folder_path, '*.csv'))\n",
|
||||
"\n",
|
||||
"# Read and concat all CSVs\n",
|
||||
"df_draft = pd.concat([pd.read_csv(file) for file in csv_files], ignore_index=True)\n",
|
||||
"\n",
|
||||
"df_draft['match'] = 1 \n",
|
||||
"\n",
|
||||
"df_draft.shape\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0c8c7493",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_draft = df_draft.drop(columns=['Categoria'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "91298cde",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_draft.columns[7:25]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "34e179cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define as colunas mensais\n",
|
||||
"colunas_mensais = df_draft.columns[7:25]\n",
|
||||
"\n",
|
||||
"# Agrupa por PDV e calcula crescimento médio por PDV\n",
|
||||
"def calcular_crescimento(grupo):\n",
|
||||
" soma_mensal = grupo[colunas_mensais].sum() # soma por mês\n",
|
||||
" variacao_mensal = soma_mensal.pct_change().dropna() # variação percentual mês a mês\n",
|
||||
" variacao_mensal = variacao_mensal[np.isfinite(variacao_mensal)]\n",
|
||||
"\n",
|
||||
" if len(variacao_mensal) == 0:\n",
|
||||
" return pd.Series({'CRESCIMENTO': np.nan})\n",
|
||||
"\n",
|
||||
" media = variacao_mensal.mean()\n",
|
||||
" desvio = variacao_mensal.std()\n",
|
||||
"\n",
|
||||
" limite_sup = media + 2 * desvio\n",
|
||||
" limite_inf = media - 2 * desvio\n",
|
||||
"\n",
|
||||
" variacoes_filtradas = variacao_mensal[variacao_mensal.between(limite_inf, limite_sup)]\n",
|
||||
" crescimento = round(variacoes_filtradas.mean(), 4)\n",
|
||||
" return pd.Series({'CRESCIMENTO': crescimento})\n",
|
||||
"\n",
|
||||
"# Aplica a função por PDV\n",
|
||||
"crescimento_por_pdv = df_draft.groupby('PDV').apply(calcular_crescimento)\n",
|
||||
"\n",
|
||||
"# Merge do resultado de volta no dataframe original\n",
|
||||
"df_draft = df_draft.merge(crescimento_por_pdv, on='PDV', how='left')\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4bc8c2b4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_similares['PDV'] = df_similares['PDV'].astype('Int64')\n",
|
||||
"\n",
|
||||
"df_final = pd.merge(left=df_similares,right=df_draft,right_on=['PDV', 'SKU'],left_on=['PDV','PRODUTO SIMILAR'],how='left')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c1451562",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_venda_diaria = pd.read_excel(r\"C:\\Users\\joao.herculano\\GRUPO GINSENG\\Assistência Suprimentos - 2025\\SUPRIMENTOS\\BD_LANÇAMENTOS\\BOT\\BOT - C11\\arquivos para geração da sugestão\\VENDAS_DIARIAS\\FormFiltroConsultaVendaSintetica_22_05_2025_16_26_17.xls\")\n",
|
||||
"\n",
|
||||
"df_venda_diaria.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "882e68aa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_venda_diaria.columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c7ddaf20",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_venda_diaria['PDV'] = df_venda_diaria['Unidade de Negócio'].str.split(\"-\").str[0].str.strip()\n",
|
||||
"\n",
|
||||
"df_venda_diaria['Dia'] = pd.to_datetime(df_venda_diaria['Dia'], format='%d/%m/%Y')\n",
|
||||
"\n",
|
||||
"df_venda_diaria = pd.merge(left=df_venda_diaria,right=calendario[['Ciclo','Date']],left_on='Dia',right_on='Date',how='inner')\n",
|
||||
"\n",
|
||||
"df_venda_diaria = df_venda_diaria.drop(columns='Date')\n",
|
||||
"\n",
|
||||
"df_venda_diaria.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7119556a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 'Dia' já está em formato datetime, então renomeamos para 'Data' diretamente\n",
|
||||
"# ou apenas usamos 'Dia' como referência de data\n",
|
||||
"\n",
|
||||
"# Ordena o DataFrame para garantir que a cumulativa funcione corretamente\n",
|
||||
"df_venda_diaria = df_venda_diaria.sort_values(by=['Unidade de Negócio', 'Código do Produto', 'Dia'])\n",
|
||||
"\n",
|
||||
"# Calcula a quantidade acumulada até o dia para cada grupo\n",
|
||||
"df_venda_diaria['Quantidade Acumulada'] = (\n",
|
||||
" df_venda_diaria\n",
|
||||
" .groupby(['Unidade de Negócio', 'Código do Produto'])['Quantidade']\n",
|
||||
" .cumsum()\n",
|
||||
") # acumulado por grupo até a data da linha\n",
|
||||
"\n",
|
||||
"df_venda_diaria.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c707a1b6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_venda_diaria = df_venda_diaria.drop_duplicates()\n",
|
||||
"\n",
|
||||
"df_venda_agrupado = df_venda_diaria.groupby(['PDV', 'Código do Produto','Ciclo'])['Quantidade Acumulada'].max().reset_index()\n",
|
||||
"df_venda_agrupado"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dc452c72",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = pd.merge(left=df_final, right=filtered_calendario[['Ciclo','INICIO CICLO','FIM CICLO','DURAÇÃO','match','dias_ate_inicio']], right_on='match',left_on='MATCH',how='left')\n",
|
||||
"df_final.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c260e0e3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#df_final = df_final.drop(columns=['PDV DESC','status','SKU','Descrição','Lançamento','Item analisado','Planograma','Quantidade por caixa'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8a05450c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = pd.merge(left=df_final, right=calendario[['Ciclo','INICIO CICLO','FIM CICLO','DURAÇÃO']], right_on='Ciclo',left_on='CICLO SIMILAR',how='left')\n",
|
||||
"df_final.shape\n",
|
||||
"\n",
|
||||
"df_final = df_final.drop_duplicates()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cc65edab",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"df_venda_agrupado = df_venda_agrupado.rename(columns={'Quantidade Acumulada':'Vendas Ciclo Lançamento'})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c5cd5f42",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final['PRODUTO LANÇAMENTO'] = df_final['PRODUTO LANÇAMENTO'].astype('Int64')\n",
|
||||
"\n",
|
||||
"df_venda_agrupado['PDV'] = df_venda_agrupado['PDV'].astype('Int64')\n",
|
||||
"\n",
|
||||
"df_final = pd.merge(left=df_final, right = df_venda_agrupado, right_on=['Ciclo','Código do Produto','PDV'],left_on=['CICLO SIMILAR','PRODUTO SIMILAR','PDV'],how='left')\n",
|
||||
"\n",
|
||||
"df_final = df_final.drop_duplicates()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "69c88d20",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final['PDV'].value_counts().min()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f5206f50",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.columns[29:46]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0a1bb832",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Suponha que os meses estão nas colunas 10 a 26 (17 colunas = 17 meses)\n",
|
||||
"colunas_mensais = df_final.columns[29:46]\n",
|
||||
"\n",
|
||||
"# Passo 1: Soma todas as linhas (itens) por mês → resultado: total por mês\n",
|
||||
"soma_mensal = df_final[colunas_mensais].sum()\n",
|
||||
"\n",
|
||||
"# Passo 2: Calcula a variação percentual de um mês para o outro\n",
|
||||
"variacao_mensal = soma_mensal.pct_change()\n",
|
||||
"variacao_mensal = variacao_mensal.dropna()\n",
|
||||
"\n",
|
||||
"variacao_mensal = variacao_mensal[np.isfinite(variacao_mensal)]\n",
|
||||
"\n",
|
||||
"# Passo 3: Calcula a média da variação (ignorando o primeiro NaN)\n",
|
||||
"media_variacao = variacao_mensal[1:].mean()\n",
|
||||
"\n",
|
||||
"# Calcula média e desvio padrão\n",
|
||||
"media = variacao_mensal.mean()\n",
|
||||
"desvio = variacao_mensal.std()\n",
|
||||
"\n",
|
||||
"# Define limite (ex: 2 desvios padrão)\n",
|
||||
"limite_superior = media + 2 * desvio\n",
|
||||
"limite_inferior = media - 2 * desvio\n",
|
||||
"\n",
|
||||
"# Filtra dados dentro do limite\n",
|
||||
"filtro = variacao_mensal.between(limite_inferior, limite_superior)\n",
|
||||
"df_filtrado = variacao_mensal[filtro]\n",
|
||||
"CRESCIMENTO = round(df_filtrado.mean(),4)\n",
|
||||
"\n",
|
||||
"df_final['CRESCIMENTO_GERAL'] = CRESCIMENTO\n",
|
||||
"\n",
|
||||
"CRESCIMENTO\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a9647c32",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = df_final.drop(columns='Ciclo_y')\n",
|
||||
"\n",
|
||||
"df_final = df_final.rename(columns={'Ciclo_x': 'Ciclo',\t'INICIO CICLO_x': 'INICIO CICLO',\t'FIM CICLO_x':'FIM CICLO' ,'DURAÇÃO_x':'DURAÇÃO',\n",
|
||||
" \t'INICIO CICLO_y': 'INICIO CICLO SIMILAR' ,\t'FIM CICLO_y': 'FIM CICLO SIMILAR','DURAÇÃO_y':'DURAÇÃO CICLO SIMILAR'})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b107e519",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.columns[40:47]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8290853c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"VENDA_SIMILAR_6_MESES= df_final.columns[40:47]\n",
|
||||
"\n",
|
||||
"df_final['Pico Vendas Similar Ultimos 6 ciclos'] = df_final[VENDA_SIMILAR_6_MESES].max(axis=1)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"df_final['MEDIANA DO HISTÓRICO'] = df_final[colunas_mensais].dropna().median(axis=1)\n",
|
||||
"\n",
|
||||
"df_final['Vendas Ciclo Lançamento'] = df_final['Vendas Ciclo Lançamento'].fillna(0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "07f043f2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"medi = df_final.groupby(['CANAL'])['MEDIANA DO HISTÓRICO'].max().reset_index()\n",
|
||||
"medi = medi.rename(columns={'MEDIANA DO HISTÓRICO':'med_por_canal'})\n",
|
||||
"medi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "94abddce",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = pd.merge(left=df_final, right=medi,on='CANAL',how='inner')\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "09cc2f82",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_vdc = pd.read_csv(r\"C:\\Users\\joao.herculano\\GRUPO GINSENG\\Assistência Suprimentos - 2025\\SUPRIMENTOS\\BD_LANÇAMENTOS\\BOT\\BOT - C11\\arquivos para geração da sugestão\\VENDAS VDC\\vendas_vdc22.02.csv\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"df_vdc['DATA VENDA'] = pd.to_datetime(df_vdc['DATA VENDA'])\n",
|
||||
"\n",
|
||||
"# 'Dia' já está em formato datetime, então renomeamos para 'Data' diretamente\n",
|
||||
"# ou apenas usamos 'Dia' como referência de data\n",
|
||||
"\n",
|
||||
"# Ordena o DataFrame para garantir que a cumulativa funcione corretamente\n",
|
||||
"df_venda_diaria = df_venda_diaria.sort_values(by=['Unidade de Negócio', 'Código do Produto', 'Dia'])\n",
|
||||
"\n",
|
||||
"# Calcula a quantidade acumulada até o dia para cada grupo\n",
|
||||
"df_vdc['Quantidade Acumulada vdc'] = (\n",
|
||||
" df_vdc\n",
|
||||
" .groupby(['PDVDEPARA.Practico', 'Código'])['Soma de Quantidade']\n",
|
||||
" .cumsum()\n",
|
||||
") # acumulado por grupo até a data da linha"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5a827c08",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_vdc = pd.merge(left=df_vdc,right=calendario[['Date','Ciclo']],left_on='DATA VENDA',right_on='Date',how='inner')\n",
|
||||
"\n",
|
||||
"df_vdc_agrupado = df_vdc.groupby(['PDVDEPARA.Practico',\t'Código','Ciclo'])['Quantidade Acumulada vdc'].max().reset_index()\n",
|
||||
"\n",
|
||||
"df_vdc_agrupado = df_vdc_agrupado.rename(columns={'Ciclo':'Ciclo vdc'})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"df_vdc_agrupado.head()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8ec14143",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = pd.merge(left=df_final, right = df_vdc_agrupado, right_on=['Ciclo vdc','Código','PDVDEPARA.Practico'],left_on=['CICLO SIMILAR','PRODUTO SIMILAR','PDV'],how='left')\n",
|
||||
"\n",
|
||||
"df_final['Quantidade Acumulada vdc'] = df_final['Quantidade Acumulada vdc'].fillna(0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"df_final['Vendas Ciclo Lançamento'] = np.where(df_final['Quantidade Acumulada vdc']>0, df_final['Quantidade Acumulada vdc'], df_final['Vendas Ciclo Lançamento'])\n",
|
||||
"\n",
|
||||
"df_final = df_final.drop(columns='Quantidade Acumulada vdc')\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"df_final = df_final.drop(columns='Ciclo vdc')\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1a625e69",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final['CRESCIMENTO_FINAL'] = df_final['CRESCIMENTO_GERAL'] + df_final['CRESCIMENTO'] #crescimento do pdv\n",
|
||||
"\n",
|
||||
"df_final['CRESCIMENTO_FINAL'] = np.where(df_final['CRESCIMENTO_GERAL'] + df_final['CRESCIMENTO']>0.8,0.8,df_final['CRESCIMENTO_GERAL'] + df_final['CRESCIMENTO'])\n",
|
||||
"\n",
|
||||
"df_final['CRESCIMENTO_FINAL'] = np.where(df_final['CRESCIMENTO_GERAL'] + df_final['CRESCIMENTO']<0,0,df_final['CRESCIMENTO_GERAL'] + df_final['CRESCIMENTO'])\n",
|
||||
"\n",
|
||||
"df_final['MEDIANA DO HISTÓRICO'] = np.where(df_final['MEDIANA DO HISTÓRICO']==0, df_final['' \\\n",
|
||||
"'or_canal'],df_final['MEDIANA DO HISTÓRICO'])\n",
|
||||
"\n",
|
||||
"# Primeiro cálculo intermediário\n",
|
||||
"df_final['PV GINSENG'] = np.where(df_final['CRESCIMENTO_FINAL'] * df_final['Vendas Ciclo Lançamento'] + df_final['Vendas Ciclo Lançamento'] < df_final['MEDIANA DO HISTÓRICO'],\n",
|
||||
" round(df_final['CRESCIMENTO_FINAL'] * df_final['MEDIANA DO HISTÓRICO']+ df_final['MEDIANA DO HISTÓRICO'],0), \n",
|
||||
" round(df_final['CRESCIMENTO_FINAL']*df_final['Vendas Ciclo Lançamento']+df_final['Vendas Ciclo Lançamento'],0))\n",
|
||||
"\n",
|
||||
"df_final['PV GINSENG'] = np.where(df_final['PV GINSENG'].isna(),df_final['med_por_canal'] ,df_final['PV GINSENG'])\n",
|
||||
"\n",
|
||||
"df_final.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad10c069",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.drop(columns=df_final.columns[29:42],inplace=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f9bddbb1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fe73c93e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"df_final.drop(columns=['status', 'Classe', 'SKU', 'Subcategoria', 'Lançamento',\n",
|
||||
" 'Desativação','Dias sem venda',\n",
|
||||
" 'Projeção Próximo Ciclo', 'Projeção Próximo Ciclo + 1',\n",
|
||||
" 'Promoção Próximo Ciclo', 'Promoção Próximo Ciclo + 1', 'Estoque Atual',\n",
|
||||
" 'Estoque em Transito', 'Pedido Pendente',\n",
|
||||
" 'Compra inteligente semanal/Sugestão de compra',\n",
|
||||
" 'Compra inteligente Próximo Ciclo',\n",
|
||||
" 'Compra inteligente Próximo Ciclo + 1', 'Item Desativado',\n",
|
||||
" 'Data Prevista Regularização', 'Carteira Bloqueada Para Novos Pedidos',\n",
|
||||
" 'Planograma', 'Quantidade por caixa', 'Preço Sell In', 'Quantidade',\n",
|
||||
" 'Item analisado', 'match_x',\n",
|
||||
" 'CRESCIMENTO', 'Ciclo', 'INICIO CICLO', 'FIM CICLO', 'DURAÇÃO',\n",
|
||||
" 'match_y', 'dias_ate_inicio', 'INICIO CICLO SIMILAR','med_por_canal', 'CRESCIMENTO_FINAL',\n",
|
||||
" 'FIM CICLO SIMILAR', 'DURAÇÃO CICLO SIMILAR', 'Código do Produto','CRESCIMENTO_GERAL'],inplace=True)\n",
|
||||
"df_final.columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "66772a9a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.columns[23:28]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "15b7149f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = df_final.rename(columns={df_final.columns[23]: \"C-4\", df_final.columns[24]: \"C-3\",df_final.columns[25]: \"C-2\",df_final.columns[26]: \"C-1\",df_final.columns[27]:'VENDAS CICLO ATUAL'})\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9333bc77",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.columns = df_final.columns.str.upper()\n",
|
||||
"\n",
|
||||
"df_final.drop(columns=df_final.filter(regex='HISTÓRICO DE VENDAS DO CICLO').columns, inplace=True)\n",
|
||||
"\n",
|
||||
"df_final.columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5abd4bae",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = df_final.drop(columns=['DESCRIÇÃO','MEDIANA DO HISTÓRICO'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "62ce5c62",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "25cbff26",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final = df_final.reindex(columns=[\n",
|
||||
" 'SUPERVISOR',\n",
|
||||
" 'ANALISTA',\n",
|
||||
" 'CANAL',\n",
|
||||
" 'UF',\n",
|
||||
" 'PDV',\n",
|
||||
" 'PDV DESC',\n",
|
||||
" 'PRODUTO LANÇAMENTO',\n",
|
||||
" 'DESCRIÇÃO DO LANÇAMENTO',\n",
|
||||
" 'MARCA',\n",
|
||||
" 'CATEGORIA',\n",
|
||||
" 'MECANICA CONSUMIDOR',\n",
|
||||
" '% CONSUMIDOR',\n",
|
||||
" 'MECANICA REVENDEDOR',\n",
|
||||
" '% REVENDEDOR',\n",
|
||||
" 'TIPO DE PRODUTO',\n",
|
||||
" 'IAF',\n",
|
||||
" 'FOCO',\n",
|
||||
" 'SKU',\n",
|
||||
" 'PRODUTO SIMILAR',\n",
|
||||
" 'DESCRIÇÃO SIMILAR',\n",
|
||||
" 'CICLO SIMILAR',\n",
|
||||
" 'VENDAS CICLO LANÇAMENTO',\n",
|
||||
" 'C-4',\n",
|
||||
" 'C-3',\n",
|
||||
" 'C-2',\n",
|
||||
" 'C-1',\n",
|
||||
" 'VENDAS CICLO ATUAL',\n",
|
||||
" 'PICO VENDAS SIMILAR ULTIMOS 6 CICLOS',\n",
|
||||
" 'PV GINSENG'])\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a3e80cb4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final['SUGESTÃO METASELLIN'] = ''\n",
|
||||
"df_final['SUGESTÃO ABASTECIMENTO'] = ''\n",
|
||||
"df_final['SUGESTÃO COMERCIAL'] = ''\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2df3e2e9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_final.to_excel(r'C:\\Users\\joao.herculano\\Documents\\sugest.xlsx',index=False)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
221
Ruptura_Projetada/ruptura projetada 23.05.py
Normal file
221
Ruptura_Projetada/ruptura projetada 23.05.py
Normal file
@ -0,0 +1,221 @@
|
||||
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.")
|
||||
1644
promoção/promoção_EUD_ciclo07.ipynb
Normal file
1644
promoção/promoção_EUD_ciclo07.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
1224
promoção/promoção_boti_ciclo07.ipynb
Normal file
1224
promoção/promoção_boti_ciclo07.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
241
relatório_improdutivo/teste email excel.py
Normal file
241
relatório_improdutivo/teste email excel.py
Normal file
@ -0,0 +1,241 @@
|
||||
# enviar_email_excel.py
|
||||
|
||||
import smtplib
|
||||
import ssl
|
||||
import psycopg2
|
||||
import configparser
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.ticker as mtick
|
||||
import seaborn as sns
|
||||
from email.message import EmailMessage
|
||||
from email.utils import make_msgid
|
||||
from email.mime.image import MIMEImage
|
||||
from pathlib import Path
|
||||
from datetime import datetime, time
|
||||
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(r"C:\Users\joao.herculano\Documents\Enviador de email\credenciais.ini")
|
||||
|
||||
print(config['banco']['host'],config['banco']['user'],config['banco']['password'])
|
||||
|
||||
# Conexão com o banco
|
||||
conn = psycopg2.connect(
|
||||
host=config['banco']['host'], # ou IP do servidor
|
||||
port="5432", # padrão do PostgreSQL
|
||||
database="ginseng",
|
||||
user=config['banco']['user'],
|
||||
password= config['banco']['password']
|
||||
)
|
||||
|
||||
# Criar um cursor para executar comandos SQL
|
||||
cur = conn.cursor()
|
||||
|
||||
|
||||
# 1. Criar dados fictícios e gerar Excel
|
||||
query = '''
|
||||
select
|
||||
*,
|
||||
CASE
|
||||
WHEN dayswithoutsales BETWEEN 40 AND 59 THEN 'mais de 40 dias'
|
||||
WHEN dayswithoutsales BETWEEN 60 AND 79 THEN 'mais de 60 dias'
|
||||
WHEN dayswithoutsales BETWEEN 80 AND 99 THEN 'mais de 80 dias'
|
||||
WHEN dayswithoutsales >= 100 THEN 'acima de 100 dias'
|
||||
ELSE 'menos de 40 dias'
|
||||
end as status_venda,
|
||||
pricesellin * (stock_actual + stock_intransit) AS valor_estoque_parado
|
||||
from "public"."draft"
|
||||
where dayswithoutsales > 40
|
||||
and deactivation = ''
|
||||
and stock_actual > 0
|
||||
and isproductdeactivated is not null
|
||||
and currentcyclesales = 0
|
||||
'''
|
||||
df = pd.read_sql(query, conn)
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
remetente = config['credenciais']['remetente']
|
||||
senha = config['credenciais']['senha']
|
||||
destinatarios = [email.strip() for email in config['email']['destinatarios'].split(',')]
|
||||
assunto = config['email']['assunto']
|
||||
|
||||
print(remetente,senha,destinatarios,assunto)
|
||||
|
||||
pdvs = pd.read_excel(r"C:\Users\joao.herculano\Documents\PDV_ATT.xlsx")
|
||||
|
||||
df['loja_id'] = df['loja_id'].astype('Int64')
|
||||
pdvs['PDV'] = pdvs['PDV'].astype('Int64')
|
||||
|
||||
|
||||
df2= pd.merge(left=df,right=pdvs[['PDV','UF']],left_on='loja_id',right_on='PDV',how='inner')
|
||||
|
||||
# Dicionário de renomeação
|
||||
colunas_traduzidas = {
|
||||
"loja_id": "id_loja",
|
||||
"code": "código",
|
||||
"description": "descrição",
|
||||
"launch": "lançamento",
|
||||
"deactivation": "desativação",
|
||||
"thirdtolastcyclesales": "venda_terceiro_ciclo_passado",
|
||||
"secondtolastcyclesales": "venda_penúltimo_ciclo",
|
||||
"lastcyclesales": "venda_último_ciclo",
|
||||
"currentcyclesales": "venda_ciclo_atual",
|
||||
"nextcycleprojection": "projeção_próximo_ciclo",
|
||||
"secondtonextcycleprojection": "projeção_segundo_próximo_ciclo",
|
||||
"stock_actual": "estoque_atual",
|
||||
"stock_intransit": "estoque_em_transito",
|
||||
"purchasesuggestion": "sugestao_compra",
|
||||
"smartpurchase_purchasesuggestioncycle": "compra_inteligente_ciclo",
|
||||
"smartpurchase_nextcyclepurchasesuggestion": "compra_inteligente_prox_ciclo",
|
||||
"pendingorder": "pedido_pendente",
|
||||
"salescurve": "curva_vendas",
|
||||
"promotions_description": "descrição_promocao",
|
||||
"promotions_discountpercent": "desconto_promocao_percentual",
|
||||
"pricesellin": "preço_sellin",
|
||||
"businessunit": "unidade_negócio",
|
||||
"codcategory": "código_categoria",
|
||||
"criticalitem_dtprovidedregularization": "dt_regularização_item_critico",
|
||||
"criticalitem_blockedwallet": "carteira_bloqueada_item_critico",
|
||||
"criticalitem_iscritical": "é_item_critico",
|
||||
"codsubcategory": "código_subcategoria",
|
||||
"isproductdeactivated": "produto_desativado",
|
||||
"brandgroupcode": "código_grupo_marca",
|
||||
"dayswithoutsales": "dias_sem_venda",
|
||||
"coveragedays": "dias_cobertura",
|
||||
"hascoverage": "tem_cobertura"
|
||||
}
|
||||
|
||||
# Renomeando as colunas do DataFrame
|
||||
df2 = df2.rename(columns=colunas_traduzidas)
|
||||
|
||||
excel_path = Path("relatorio.xlsx")
|
||||
df2.to_excel(excel_path, index=False)
|
||||
|
||||
|
||||
# 2. Criar e salvar gráfico
|
||||
plot_df = df2.groupby('UF')['valor_estoque_parado'].sum().reset_index()
|
||||
plt.figure(figsize=(6, 4))
|
||||
ax = sns.barplot(data=plot_df, x='UF', y='valor_estoque_parado', errorbar=None)
|
||||
ax.yaxis.set_major_formatter(mtick.StrMethodFormatter('R${x:,.2f}'))
|
||||
for p in ax.patches:
|
||||
valor = p.get_height()
|
||||
ax.annotate(f'R$ {valor:,.2f}', (p.get_x() + p.get_width() / 2, valor),
|
||||
ha='center', va='bottom', fontsize=9)
|
||||
plt.title("Estoque parado por UF")
|
||||
plt.ylabel("Valor em Reais")
|
||||
plt.xlabel("UF")
|
||||
plt.tight_layout()
|
||||
plt.savefig("grafico.png")
|
||||
plt.close()
|
||||
|
||||
# Obtém a hora atual
|
||||
agora = datetime.now().time()
|
||||
# Define os intervalos de tempo
|
||||
manhã_inicio = time(5, 0)
|
||||
manhã_fim = time(12, 0)
|
||||
tarde_inicio = time(12, 1)
|
||||
tarde_fim = time(18, 0)
|
||||
# noite é dividida em dois intervalos por causa da virada do dia
|
||||
noite_inicio = time(18, 1)
|
||||
noite_fim = time(4, 59)
|
||||
# Verifica em qual intervalo a hora atual está
|
||||
if manhã_inicio <= agora <= manhã_fim:
|
||||
boa = "Bom dia!"
|
||||
elif tarde_inicio <= agora <= tarde_fim:
|
||||
boa = "Boa tarde!"
|
||||
else:
|
||||
boa = "Boa noite!"
|
||||
|
||||
df2['DATA'] = agora
|
||||
|
||||
df3 = df2.groupby('DATA', as_index=False)['valor_estoque_parado'].sum()
|
||||
|
||||
path2 = r'C:\Users\joao.herculano\OneDrive - GRUPO GINSENG\Documentos\acompanhamentos\40D sem Venda\acompanhamento40DSV.xlsx'
|
||||
|
||||
# Tenta abrir e escrever com append
|
||||
with pd.ExcelWriter(path2, mode='a', engine='openpyxl', if_sheet_exists='overlay') as writer:
|
||||
# Encontra a última linha preenchida
|
||||
book = writer.book
|
||||
sheet = writer.sheets['Sheet1'] if 'Sheet1' in writer.sheets else writer.book.active
|
||||
start_row = sheet.max_row
|
||||
|
||||
# Escreve sem cabeçalho se não for a primeira linha
|
||||
df3.to_excel(writer, index=False, header=not start_row > 1, startrow=start_row)
|
||||
|
||||
# 3. Criar e-mail com imagem embutida
|
||||
grafico_cid = make_msgid()[1:-1] # remove < >
|
||||
msg = EmailMessage()
|
||||
msg['From'] = remetente
|
||||
msg['To'] = ', '.join(destinatarios)
|
||||
msg['Subject'] = assunto
|
||||
|
||||
|
||||
# 4. Conteúdo do e-mail
|
||||
html_email = f"""
|
||||
<html>
|
||||
<body>
|
||||
<p>{boa}</p>
|
||||
|
||||
<p>
|
||||
Segue o relatório semanal de estoque improdutivo referente aos estados de
|
||||
Alagoas (AL), Bahia (BA), Sergipe (SE) e região de Vitória da Conquista (VDC).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Este relatório contempla exclusivamente os itens que possuem saldo em estoque,
|
||||
mas que estão sem vendas há mais de 40 dias.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
O objetivo é trazer visibilidade para os produtos parados e reforçar a importância
|
||||
de ações para estimular a sua saída, contribuindo assim para a redução da cobertura
|
||||
de estoque e otimização dos recursos.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Contamos com o apoio de todos para análise e tratativa dos itens listados.
|
||||
Sugestões de ações como campanhas, transferências ou ajustes de sortimento são
|
||||
bem-vindas para acelerar a movimentação dos produtos.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Para mais informações, favor consultar a planilha em anexo.
|
||||
</p>
|
||||
|
||||
<p><b>Segue resumo:</b></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')
|
||||
|
||||
# 4. Anexar gráfico inline
|
||||
with open("grafico.png", 'rb') as img:
|
||||
msg.get_payload()[1].add_related(img.read(), 'image', 'png', cid=grafico_cid)
|
||||
|
||||
|
||||
# 5. Anexar o arquivo Excel
|
||||
with open(excel_path, 'rb') as f:
|
||||
msg.add_attachment(
|
||||
f.read(),
|
||||
maintype='application',
|
||||
subtype='vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
filename=excel_path.name
|
||||
)
|
||||
|
||||
# 6. Enviar o e-mail via SMTP Outlook com configurações fornecidas
|
||||
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.")
|
||||
221
relatório_ruptura/ruptura projetada 23.05.py
Normal file
221
relatório_ruptura/ruptura projetada 23.05.py
Normal file
@ -0,0 +1,221 @@
|
||||
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.")
|
||||
Loading…
x
Reference in New Issue
Block a user