Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,12 +1,13 @@
|
|
1 |
-
"""app.py
|
2 |
A productionโgrade BI copilot with:
|
3 |
-
โข CSVโฏ/โฏExcelโฏ/โฏParquet
|
4 |
-
โข
|
5 |
-
โข
|
6 |
-
โข Drill
|
7 |
-
โข Autoโdetected
|
8 |
-
โข One
|
9 |
"""
|
|
|
10 |
from __future__ import annotations
|
11 |
import os, io, tempfile
|
12 |
from pathlib import Path
|
@@ -19,23 +20,22 @@ from statsmodels.tsa.arima.model import ARIMA
|
|
19 |
from sqlalchemy import create_engine
|
20 |
import google.generativeai as genai
|
21 |
|
22 |
-
#
|
23 |
API_KEY = st.secrets.get("GEMINI_APIKEY") or os.getenv("GEMINI_APIKEY")
|
24 |
if not API_KEY:
|
25 |
-
st.error("โ
|
26 |
st.stop()
|
27 |
|
|
|
28 |
genai.configure(api_key=API_KEY)
|
29 |
GEM_MODEL = "gemini-1.5-pro-latest"
|
30 |
TMP = Path(tempfile.gettempdir())
|
31 |
|
32 |
-
|
33 |
-
|
34 |
-
# โโโโโโโโโโโโโโโโโโโ 1โฏยทโฏUTILITY HELPERS โโโโโโโโโโโโโโโโโโโโโ
|
35 |
@st.cache_data(show_spinner=False)
|
36 |
def read_file(buf: io.BufferedReader, sample: bool = False) -> pd.DataFrame:
|
37 |
suf = Path(buf.name).suffix.lower()
|
38 |
-
if suf in {".xls", ".xlsx"}:
|
39 |
return pd.read_excel(buf, engine="openpyxl")
|
40 |
if suf == ".parquet":
|
41 |
return pd.read_parquet(buf)
|
@@ -53,92 +53,94 @@ def read_table(uri: str, tbl: str) -> pd.DataFrame:
|
|
53 |
def ask_gemini(prompt: str) -> str:
|
54 |
return genai.GenerativeModel(GEM_MODEL).generate_content(prompt).text.strip()
|
55 |
|
56 |
-
#
|
57 |
-
st.title("๐ BizIntel
|
58 |
-
mode = st.sidebar.radio("Source", ["File", "SQL"], horizontal=True)
|
59 |
-
|
60 |
|
61 |
-
if mode == "File":
|
62 |
-
upl = st.sidebar.file_uploader("Upload CSV
|
63 |
-
sample = st.sidebar.checkbox("Load sample
|
64 |
if upl:
|
65 |
-
|
66 |
else:
|
67 |
uri = st.sidebar.text_input("SQLAlchemy URI")
|
68 |
if uri:
|
69 |
-
tbl = st.sidebar.selectbox("Table", sql_tables(uri))
|
70 |
if tbl:
|
71 |
-
|
72 |
|
73 |
-
if
|
74 |
-
st.info("โฌ
๏ธ
|
75 |
st.stop()
|
76 |
|
77 |
-
st.success("โ
|
78 |
-
st.dataframe(
|
79 |
|
80 |
-
#
|
81 |
-
rows, cols =
|
82 |
-
|
83 |
-
c1,c2,c3 = st.columns(3)
|
84 |
c1.metric("Rows", f"{rows:,}")
|
85 |
c2.metric("Columns", cols)
|
86 |
-
c3.metric("Missing
|
87 |
|
88 |
st.subheader("๐ง Gemini Insights")
|
89 |
-
with st.spinner("
|
90 |
-
summary =
|
91 |
st.markdown(ask_gemini(
|
92 |
-
"You are a senior BI analyst.
|
93 |
))
|
94 |
|
95 |
-
#
|
96 |
-
#
|
97 |
-
for c in
|
98 |
-
if not pd.api.types.is_datetime64_any_dtype(
|
99 |
try:
|
100 |
-
|
101 |
-
except:
|
102 |
-
pass
|
103 |
|
104 |
-
DATE_COL = st.selectbox("Date column", [c for c in
|
105 |
-
METRIC_COL = st.selectbox("Numeric metric", [c for c in
|
106 |
|
107 |
-
|
108 |
-
|
109 |
.groupby(DATE_COL)[METRIC_COL].mean().sort_index()
|
110 |
)
|
111 |
-
fig_ts = px.line(
|
112 |
st.plotly_chart(fig_ts, use_container_width=True)
|
113 |
|
114 |
-
#
|
115 |
st.subheader("๐ฎ Forecast")
|
116 |
-
steps = st.slider("Horizon", 3, 365, 90)
|
117 |
-
p = st.number_input("
|
118 |
-
|
119 |
-
|
120 |
-
|
|
|
|
|
|
|
121 |
forecast = pd.Series(model.forecast(steps), index=fut_idx)
|
122 |
-
fig_fc = px.line(pd.concat([
|
123 |
st.plotly_chart(fig_fc, use_container_width=True)
|
124 |
|
125 |
-
#
|
126 |
-
st.subheader("๐
|
127 |
-
with st.expander("Histogram
|
128 |
-
col = st.selectbox("
|
129 |
-
st.plotly_chart(px.histogram(
|
130 |
-
|
131 |
-
|
132 |
-
|
|
|
133 |
|
134 |
-
#
|
135 |
brief = (
|
136 |
"# Strategy Brief\n"
|
137 |
-
"* Clean missing
|
138 |
-
"* Investigate
|
139 |
-
"*
|
140 |
-
"*
|
141 |
-
"* Segment
|
142 |
)
|
143 |
-
st.download_button("โฌ๏ธ
|
144 |
-
|
|
|
1 |
+
"""app.py โ BizIntel AI Ultra (Geminiโonly, v4)
|
2 |
A productionโgrade BI copilot with:
|
3 |
+
โข CSVโฏ/โฏExcelโฏ/โฏParquet and SQL ingestion
|
4 |
+
โข Smart sampling + memoryโsafe loading for large files
|
5 |
+
โข Schema + missing-data audit with Gemini-generated insights
|
6 |
+
โข Drill-down EDA (histogram, violin, scatter-matrix, heatmap)
|
7 |
+
โข Autoโdetected date column, tunable ARIMA forecasting
|
8 |
+
โข One-click strategy brief download (Markdown)
|
9 |
"""
|
10 |
+
|
11 |
from __future__ import annotations
|
12 |
import os, io, tempfile
|
13 |
from pathlib import Path
|
|
|
20 |
from sqlalchemy import create_engine
|
21 |
import google.generativeai as genai
|
22 |
|
23 |
+
# โโโโโโโโโโโโโโโโโโโโโ 0โฏยทโฏCONFIGURATION โโโโโโโโโโโโโโโโโโโโโ
|
24 |
API_KEY = st.secrets.get("GEMINI_APIKEY") or os.getenv("GEMINI_APIKEY")
|
25 |
if not API_KEY:
|
26 |
+
st.error("โ Missing `GEMINI_APIKEY` โ add it in Settings โ Secrets or set env variable.")
|
27 |
st.stop()
|
28 |
|
29 |
+
st.set_page_config("BizIntelย AIย Ultra", "๐", "wide", initial_sidebar_state="expanded")
|
30 |
genai.configure(api_key=API_KEY)
|
31 |
GEM_MODEL = "gemini-1.5-pro-latest"
|
32 |
TMP = Path(tempfile.gettempdir())
|
33 |
|
34 |
+
# โโโโโโโโโโโโโโโโโโโโโ 1โฏยทโฏUTILITY HELPERS โโโโโโโโโโโโโโโโโโโ
|
|
|
|
|
35 |
@st.cache_data(show_spinner=False)
|
36 |
def read_file(buf: io.BufferedReader, sample: bool = False) -> pd.DataFrame:
|
37 |
suf = Path(buf.name).suffix.lower()
|
38 |
+
if suf in {".xls", ".xlsx"}:
|
39 |
return pd.read_excel(buf, engine="openpyxl")
|
40 |
if suf == ".parquet":
|
41 |
return pd.read_parquet(buf)
|
|
|
53 |
def ask_gemini(prompt: str) -> str:
|
54 |
return genai.GenerativeModel(GEM_MODEL).generate_content(prompt).text.strip()
|
55 |
|
56 |
+
# โโโโโโโโโโโโโโโโโโโโโ 2โฏยทโฏDATA INGESTION โโโโโโโโโโโโโโโโโโโโ
|
57 |
+
st.title("๐ BizIntel AI Ultra โ Gemini 1.5 Pro BI Copilot")
|
58 |
+
mode = st.sidebar.radio("Select Data Source", ["Upload File", "SQL Database"], horizontal=True)
|
59 |
+
df: pd.DataFrame = pd.DataFrame()
|
60 |
|
61 |
+
if mode == "Upload File":
|
62 |
+
upl = st.sidebar.file_uploader("Upload CSV / Excel / Parquet", ["csv", "xls", "xlsx", "parquet"], help="โค2โฏGB")
|
63 |
+
sample = st.sidebar.checkbox("Load sample (โค 5M rows)")
|
64 |
if upl:
|
65 |
+
df = read_file(upl, sample)
|
66 |
else:
|
67 |
uri = st.sidebar.text_input("SQLAlchemy URI")
|
68 |
if uri:
|
69 |
+
tbl = st.sidebar.selectbox("Choose Table", sql_tables(uri))
|
70 |
if tbl:
|
71 |
+
df = read_table(uri, tbl)
|
72 |
|
73 |
+
if df.empty:
|
74 |
+
st.info("โฌ
๏ธ Load a dataset to get started.")
|
75 |
st.stop()
|
76 |
|
77 |
+
st.success("โ
Data loaded")
|
78 |
+
st.dataframe(df.head(), use_container_width=True)
|
79 |
|
80 |
+
# โโโโโโโโโโโโโโโโโโโโโ 3โฏยทโฏSUMMARY + GEMINI โโโโโโโโโโโโโโโโโโโ
|
81 |
+
rows, cols = df.shape
|
82 |
+
miss_pct = df.isna().sum().sum() / (rows * cols) * 100
|
83 |
+
c1, c2, c3 = st.columns(3)
|
84 |
c1.metric("Rows", f"{rows:,}")
|
85 |
c2.metric("Columns", cols)
|
86 |
+
c3.metric("Missing %", f"{miss_pct:.1f}")
|
87 |
|
88 |
st.subheader("๐ง Gemini Insights")
|
89 |
+
with st.spinner("Generating analysis..."):
|
90 |
+
summary = df.describe(include="all", datetime_is_numeric=True).round(2).to_json()
|
91 |
st.markdown(ask_gemini(
|
92 |
+
"You are a senior BI analyst. List 5 key insights and 3 action items based on this dataset: " + summary
|
93 |
))
|
94 |
|
95 |
+
# โโโโโโโโโโโโโโโโโโโโโ 4โฏยทโฏTIME SERIES SETUP โโโโโโโโโโโโโโโโโ
|
96 |
+
# try datetime coercion
|
97 |
+
for c in df.columns:
|
98 |
+
if not pd.api.types.is_datetime64_any_dtype(df[c]):
|
99 |
try:
|
100 |
+
df[c] = pd.to_datetime(df[c])
|
101 |
+
except: pass
|
|
|
102 |
|
103 |
+
DATE_COL = st.selectbox("Date column", [c for c in df.columns if pd.api.types.is_datetime64_any_dtype(df[c])])
|
104 |
+
METRIC_COL = st.selectbox("Numeric metric", [c for c in df.select_dtypes("number").columns])
|
105 |
|
106 |
+
series = (
|
107 |
+
df[[DATE_COL, METRIC_COL]].dropna()
|
108 |
.groupby(DATE_COL)[METRIC_COL].mean().sort_index()
|
109 |
)
|
110 |
+
fig_ts = px.line(series, title=f"{METRIC_COL} Trend", labels={"index": "Date", METRIC_COL: METRIC_COL})
|
111 |
st.plotly_chart(fig_ts, use_container_width=True)
|
112 |
|
113 |
+
# โโโโโโโโโโโโโโโโโโโโโ 5โฏยทโฏARIMA FORECASTING โโโโโโโโโโโโโโโโโ
|
114 |
st.subheader("๐ฎ Forecast")
|
115 |
+
steps = st.slider("Forecast Horizon", 3, 365, 90)
|
116 |
+
p = st.number_input("AR Order (p)", 0, 5, 1)
|
117 |
+
d = st.number_input("Diff Order (d)", 0, 2, 1)
|
118 |
+
q = st.number_input("MA Order (q)", 0, 5, 1)
|
119 |
+
|
120 |
+
with st.spinner("Training ARIMA model..."):
|
121 |
+
model = ARIMA(series, order=(p, d, q)).fit()
|
122 |
+
fut_idx = pd.date_range(series.index[-1], periods=steps + 1, freq=pd.infer_freq(series.index) or "D")[1:]
|
123 |
forecast = pd.Series(model.forecast(steps), index=fut_idx)
|
124 |
+
fig_fc = px.line(pd.concat([series, forecast.rename("Forecast")], axis=1), title="Actual vs Forecast")
|
125 |
st.plotly_chart(fig_fc, use_container_width=True)
|
126 |
|
127 |
+
# โโโโโโโโโโโโโโโโโโโโโ 6โฏยทโฏEDA TOOLS โโโโโโโโโโโโโโโโโโโโโโโโโ
|
128 |
+
st.subheader("๐ Exploratory Data Dashboard")
|
129 |
+
with st.expander("Histogram + Box"):
|
130 |
+
col = st.selectbox("Metric column", METRIC_COL, key="hist")
|
131 |
+
st.plotly_chart(px.histogram(df, x=col, marginal="box", template="plotly_dark"), use_container_width=True)
|
132 |
+
|
133 |
+
with st.expander("Correlation Heatmap"):
|
134 |
+
corr = df.select_dtypes("number").corr()
|
135 |
+
st.plotly_chart(px.imshow(corr, color_continuous_scale="RdBu", aspect="auto", title="Correlation Matrix"), use_container_width=True)
|
136 |
|
137 |
+
# โโโโโโโโโโโโโโโโโโโโโ 7โฏยทโฏSTRATEGY DOWNLOAD โโโโโโโโโโโโโโโโ
|
138 |
brief = (
|
139 |
"# Strategy Brief\n"
|
140 |
+
"* Clean missing date values for better time modeling.\n"
|
141 |
+
"* Investigate top correlations for potential drivers.\n"
|
142 |
+
"* Leverage forecast for inventory and staff planning.\n"
|
143 |
+
"* Watch for outliers >3ฯ weekly.\n"
|
144 |
+
"* Segment by region and product for precise actions."
|
145 |
)
|
146 |
+
st.download_button("โฌ๏ธ Download Strategy (.md)", brief, "bizintel_brief.md", "text/markdown")
|
|