# sheet_tool.py from google.oauth2.service_account import Credentials import gspread import pandas as pd import matplotlib.pyplot as plt import io import base64 from typing import Tuple # CONFIG - change these CREDENTIALS_FILE = "credentials.json" # path to your service account JSON SHEET_ID = "1nOekWGmPsjoHj9T-MFjlNGSFyKPRogVbEjFNRxGgxuM" # replace with your sheet id WORKSHEET_INDEX = 0 # first sheet SCOPES = [ "https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive", ] def authorize_gs(): creds = Credentials.from_service_account_file(CREDENTIALS_FILE, scopes=SCOPES) client = gspread.authorize(creds) return client def fetch_sheet_as_df(sheet_id: str = SHEET_ID, worksheet_index: int = WORKSHEET_INDEX) -> pd.DataFrame: client = authorize_gs() sh = client.open_by_key(sheet_id) worksheet = sh.get_worksheet(worksheet_index) data = worksheet.get_all_values() if not data: return pd.DataFrame() df = pd.DataFrame(data[1:], columns=data[0]) # try to convert numeric columns where possible for col in df.columns: df[col] = pd.to_numeric(df[col], errors="ignore") return df def create_pivot(df: pd.DataFrame, index: list, columns: list, values: list, aggfunc: str = "sum") -> pd.DataFrame: if df.empty: return pd.DataFrame() # Create pivot table pivot = pd.pivot_table(df, index=index, columns=columns, values=values, aggfunc=aggfunc, fill_value=0) # Flatten multi-index columns for readability pivot = pivot.reset_index() pivot.columns = [ " ".join(map(str, c)).strip() if isinstance(c, tuple) else str(c).strip() for c in pivot.columns ] return pivot def summary_stats(df: pd.DataFrame, numeric_only: bool = True) -> pd.DataFrame: if df.empty: return pd.DataFrame() return df.describe(include="all") if not numeric_only else df.describe() def plot_dataframe(df: pd.DataFrame, kind: str = "bar", x: str = None, y: list = None, title: str = None, figsize=(8,5)) -> str: """ Creates a matplotlib plot and returns a base64 PNG data URI. """ if df.empty: raise ValueError("DataFrame is empty") plt.close('all') fig, ax = plt.subplots(figsize=figsize) if kind == "bar": if x is None or y is None: df.plot(kind="bar", ax=ax) else: df.plot(kind="bar", x=x, y=y, ax=ax) elif kind == "line": df.plot(kind="line", x=x, y=y, ax=ax) elif kind == "pie": df.set_index(x)[y].plot(kind="pie", ax=ax, autopct='%1.1f%%') else: df.plot(kind=kind, x=x, y=y, ax=ax) if title: ax.set_title(title) ax.grid(True) buf = io.BytesIO() fig.tight_layout() fig.savefig(buf, format="png") buf.seek(0) b64 = base64.b64encode(buf.read()).decode("utf-8") return "data:image/png;base64," + b64 def df_to_csv_bytes(df: pd.DataFrame) -> bytes: return df.to_csv(index=False).encode("utf-8")