import gradio as gr import pandas as pd import plotly.express as px from prophet import Prophet # Ссылки на CSV-файлы URL_DASHA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Dasha_2025/main/messages.csv" URL_LERA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Lera_2025/main/messages.csv" URL_SVETA = "https://raw.githubusercontent.com/fruitpicker01/Storage_Sveta_2025/main/messages.csv" def read_and_process_data(url, user_name): """ Возвращает: 1) unique_count: количество уникальных SMS (по gender, generation, industry, opf) 2) df_daily: дата, пользователь, дневное кол-во уникальных SMS (НЕ накопленное), уже после удаления дубликатов по 4 столбцам. """ df = pd.read_csv(url, na_values=["Не выбрано"]) cols = ["gender", "generation", "industry", "opf", "timestamp"] df = df[[c for c in cols if c in df.columns]].copy() # Убираем дубликаты по ключевым столбцам df_unique = df.drop_duplicates(subset=["gender", "generation", "industry", "opf"]) # Количество уникальных SMS unique_count = len(df_unique) # Преобразуем timestamp -> date if "timestamp" in df_unique.columns: df_unique["timestamp"] = pd.to_numeric(df_unique["timestamp"], errors='coerce') df_unique["date"] = pd.to_datetime(df_unique["timestamp"], unit="s", origin="unix", errors='coerce').dt.date else: df_unique["date"] = pd.NaT # Сгруппируем по дате, чтобы получить кол-во за каждый день df_daily = df_unique.groupby("date").size().reset_index(name="count") df_daily["user"] = user_name return unique_count, df_daily def make_average_forecast(total_by_date, end_date_str="2025-02-28"): """ Строит «прогноз по среднему» до указанной даты (end_date_str), считая средний дневной прирост (по всем дням, без исключения выходных). Возвращает DataFrame с колонками ["ds", "yhat"], начиная с (last_date+1) по end_date_str (включительно). """ if total_by_date.empty: return pd.DataFrame(columns=["ds", "yhat"]) df_tmp = total_by_date.copy() df_tmp["date"] = pd.to_datetime(df_tmp["date"]) # Средний дневной прирост (столбец "count") по всем дням avg_inc = df_tmp["count"].mean() if len(df_tmp) else 0 last_date = df_tmp["date"].max() last_cumulative = df_tmp["cumulative"].iloc[-1] end_date = pd.to_datetime(end_date_str) # Движемся по календарю день за днём current_date = last_date forecast_data = [] running_total = last_cumulative while current_date < end_date: current_date += pd.Timedelta(days=1) if current_date > end_date: break # Прибавляем средний прирост независимо от выходного или буднего дня running_total += avg_inc forecast_data.append({"ds": current_date, "yhat": running_total}) return pd.DataFrame(forecast_data) def process_data(): # Шаг 1: Считываем CSV по каждому репозиторию (Даша, Лера, Света) dasha_count, dasha_daily = read_and_process_data(URL_DASHA, "Даша") lera_count, lera_daily = read_and_process_data(URL_LERA, "Лера") sveta_count, sveta_daily = read_and_process_data(URL_SVETA, "Света") # Сумма total_count = dasha_count + lera_count + sveta_count # Подсчитываем проценты dasha_percent = round((dasha_count / 234) * 100) if 234 else 0 lera_percent = round((lera_count / 234) * 100) if 234 else 0 sveta_percent = round((sveta_count / 234) * 100) if 234 else 0 total_percent = round((total_count / 702) * 100) if 702 else 0 # Генерируем HTML для прогресс-баров def get_progress_bar(label, abs_val, pct): capacity = 234 if label in ["Даша", "Лера", "Света"] else 702 return f"""
{label}
 {abs_val} SMS ({pct}% из {capacity})
""" bars_html = ( get_progress_bar("Даша", dasha_count, dasha_percent) + get_progress_bar("Лера", lera_count, lera_percent) + get_progress_bar("Света", sveta_count, sveta_percent) + get_progress_bar("Всего", total_count, total_percent) ) # Шаг 2: Готовим общий датафрейм по датам daily_all = pd.concat([dasha_daily, lera_daily, sveta_daily], ignore_index=True) daily_all = daily_all.dropna(subset=["date"]) # убираем NaT # Считаем кумулятивное значение для каждого пользователя daily_all = daily_all.sort_values(by=["user", "date"]) daily_all["cumulative"] = daily_all.groupby("user")["count"].cumsum() # «Всего» total_by_date = daily_all.groupby("date")["count"].sum().reset_index(name="count") total_by_date = total_by_date.sort_values(by="date") total_by_date["cumulative"] = total_by_date["count"].cumsum() total_by_date["user"] = "Всего" # Объединяем daily_all_final = pd.concat([daily_all, total_by_date], ignore_index=True) # Сортируем легенду: у кого итог больше, тот сверху last_values = daily_all_final.groupby("user")["cumulative"].last().sort_values(ascending=False) sorted_users = last_values.index.tolist() color_map = { "Даша": "#1f77b4", "Лера": "#2ca02c", "Света": "#d62728", "Всего": "#9467bd" } # Строим накопительный график fig = px.line( daily_all_final, x="date", y="cumulative", color="user", title="Накопительное количество SMS", labels={"date": "Дата", "cumulative": "Накопленное количество SMS", "user": "Редактор"}, category_orders={"user": sorted_users}, color_discrete_map=color_map ) # Шаг 3: Два прогноза forecast_fig = None # Если есть данные "Всего", делаем прогноз if not total_by_date.empty: df_prophet = total_by_date[["date", "cumulative"]].copy() df_prophet.columns = ["ds", "y"] df_prophet["ds"] = pd.to_datetime(df_prophet["ds"]) # Прогноз Prophet model = Prophet() model.fit(df_prophet) end_date = pd.to_datetime("2025-02-28") last_date = df_prophet["ds"].max() additional_days = (end_date - last_date).days future = model.make_future_dataframe(periods=0) # если уже после if additional_days > 0: future = model.make_future_dataframe(periods=additional_days) forecast = model.predict(future) # Совмещаем df_plot = pd.merge( forecast[["ds", "yhat"]], df_prophet[["ds", "y"]], on="ds", how="left" ) df_history = df_plot.dropna(subset=["y"]) df_future = df_plot[df_plot["y"].isna()] # Прогноз по среднему (без учёта выходных — т. е. на каждый календарный день) df_avg = make_average_forecast(total_by_date, "2025-02-28") # Общий график для сравнения forecast_fig = px.line( df_history, x="ds", y="y", title="Прогноз до конца февраля 2025 (всего)", labels={"ds": "Дата", "y": "Накопленное число SMS"} ) # Prophet-пунктир forecast_fig.add_scatter( x=df_future["ds"], y=df_future["yhat"], mode="lines", name="Прогноз (Prophet)", line=dict(dash="dash", color="red") ) # Средний-пунктир if not df_avg.empty: forecast_fig.add_scatter( x=df_avg["ds"], y=df_avg["yhat"], mode="lines", name="Прогноз (по среднему)", line=dict(dash="dash", color="green") ) forecast_fig.update_layout(showlegend=True) # Возвращаем всё в Gradio return (bars_html, fig, forecast_fig) with gr.Blocks() as demo: gr.Markdown("

Количество сохраненных SMS (Даша, Лера, Света)

") btn = gr.Button("Обновить данные и показать результат") html_output = gr.HTML(label="Прогресс-бары: количество SMS и %") plot_output = gr.Plot(label="Накопительный график (Даша, Лера, Света, Всего)") forecast_output = gr.Plot(label="Прогноз до конца февраля 2025 (всего)") btn.click(fn=process_data, outputs=[html_output, plot_output, forecast_output]) if __name__ == "__main__": demo.launch()