oneprocess 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
oneprocess/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """OneProcess: An all-in-one data processing and analytics toolkit."""
2
+ from oneprocess.core import DataProcessor
3
+ from oneprocess.visuals import GraphAnalyzer
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ __all__ = [
8
+ "__version__",
9
+ "DataProcessor",
10
+ "GraphAnalyzer",
11
+ ]
oneprocess/app.py ADDED
@@ -0,0 +1,357 @@
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import io
4
+ from oneprocess.core.processor import DataProcessor
5
+ from oneprocess.visuals.charts import GraphAnalyzer
6
+
7
+ st.set_page_config(page_title="OneProcess - Data Toolkit", layout="wide", page_icon="โšก")
8
+
9
+ st.title("โšก OneProcess")
10
+ st.caption("A Unified Data Processing, Cloud Ingestion, and Analytics Toolkit")
11
+
12
+ # Sidebar file uploader for active workspace
13
+ # Sidebar file uploader for active workspace
14
+ st.sidebar.header("๐Ÿ“ Primary Dataset")
15
+ uploaded_file = st.sidebar.file_uploader("Upload CSV or Excel", type=["csv", "xlsx", "xls"])
16
+
17
+ # Initialize session state keys
18
+ if "df" not in st.session_state:
19
+ st.session_state.df = None
20
+ if "original_df" not in st.session_state:
21
+ st.session_state.original_df = None
22
+ if "last_uploaded_file" not in st.session_state:
23
+ st.session_state.last_uploaded_file = None
24
+
25
+ # Only load when a file is uploaded AND it's new/changed
26
+ if uploaded_file is not None:
27
+ if st.session_state.last_uploaded_file != uploaded_file.name:
28
+ loaded_df = DataProcessor.read_file(uploaded_file)
29
+
30
+ # Save both current working copy and original backup
31
+ st.session_state.df = loaded_df
32
+ st.session_state.original_df = loaded_df.copy()
33
+ st.session_state.last_uploaded_file = uploaded_file.name
34
+ st.session_state.last_action = None
35
+
36
+ if st.session_state.df is not None:
37
+ st.sidebar.success(f"Loaded: {st.session_state.df.shape[0]} rows, {st.session_state.df.shape[1]} cols")
38
+ else:
39
+ # Clear state if user removes/clears the uploaded file
40
+ st.session_state.df = None
41
+ st.session_state.original_df = None
42
+ st.session_state.last_uploaded_file = None
43
+
44
+ tabs = st.tabs([
45
+ "๐Ÿ” Schema & Inspection",
46
+ "๐Ÿงน Clean & Transform",
47
+ "๐Ÿ“‘ File Merger",
48
+ "๐Ÿ“Š Bar Chart Analysis",
49
+ ])
50
+
51
+ # TAB 1: Inspection
52
+ with tabs[0]:
53
+ st.subheader("Data Column Type Identifier")
54
+ if st.session_state.df is not None:
55
+ processor = DataProcessor(st.session_state.df)
56
+ info_df = processor.identify_column_types()
57
+
58
+ # Transpose so columns are along the top and metrics are on the left
59
+ transposed_info = info_df.set_index("Column Name").T
60
+ transposed_info.index.name = "Metric"
61
+
62
+ st.dataframe(transposed_info, use_container_width=True)
63
+ st.subheader("Data Preview")
64
+ st.dataframe(st.session_state.df.head(10), use_container_width=True)
65
+ else:
66
+ st.info("Upload a file in the sidebar to inspect columns.")
67
+
68
+ # TAB 2: Cleaning (Deduplication & Dynamic Distinct Value Filtering)
69
+ with tabs[1]:
70
+ st.subheader("File Processing & Cleaning")
71
+
72
+ if "df" in st.session_state and st.session_state.df is not None:
73
+ if "data_version" not in st.session_state:
74
+ st.session_state.data_version = 0
75
+
76
+ # =========================================================
77
+ # 1. PREVIEW 1: BEFORE APPLYING (Original Uploaded File)
78
+ # =========================================================
79
+ top_header_col, top_reset_col = st.columns([4, 1])
80
+ with top_header_col:
81
+ orig_df = st.session_state.original_df
82
+ st.write(f"##### ๐Ÿ“Š Preview 1: Original Uploaded Data ({len(orig_df)} rows ร— {len(orig_df.columns)} cols)")
83
+ with top_reset_col:
84
+ if st.button("๐Ÿ”„ Reset All Changes"):
85
+ st.session_state.df = st.session_state.original_df.copy()
86
+ st.session_state.last_action = "Reset back to original upload."
87
+ st.session_state.data_version += 1
88
+ st.rerun()
89
+
90
+ st.dataframe(orig_df.head(50), use_container_width=True)
91
+ st.caption(f"Showing first {min(50, len(orig_df))} of {len(orig_df)} rows.")
92
+
93
+ st.divider()
94
+
95
+ # =========================================================
96
+ # 2. CONTROLS: DEDUPLICATION & DYNAMIC VALUE FILTERING
97
+ # =========================================================
98
+ current_working_df = st.session_state.df
99
+
100
+ # -----------------------------
101
+ # Deduplication Section
102
+ # -----------------------------
103
+ st.write("##### 1. Deduplication")
104
+ col_dedup1, col_dedup2 = st.columns([3, 1])
105
+ with col_dedup1:
106
+ cols_to_check = st.multiselect(
107
+ "Columns to check for duplicates (leave blank for all)",
108
+ current_working_df.columns,
109
+ key=f"dedup_cols_{st.session_state.data_version}"
110
+ )
111
+ with col_dedup2:
112
+ keep_strategy = st.radio("Keep record:", ["first", "last"], horizontal=True, key="keep_rad")
113
+
114
+ subset_param = cols_to_check if cols_to_check else None
115
+ dup_mask = current_working_df.duplicated(subset=subset_param, keep=False)
116
+ dup_rows = current_working_df[dup_mask]
117
+ dup_count = len(dup_rows)
118
+
119
+ if dup_count > 0:
120
+ st.warning(f"โš ๏ธ Found **{dup_count}** duplicate records.")
121
+ with st.expander("๐Ÿ” Preview Duplicate Records Only", expanded=False):
122
+ st.dataframe(
123
+ dup_rows.sort_values(by=subset_param if subset_param else list(current_working_df.columns)),
124
+ use_container_width=True
125
+ )
126
+ else:
127
+ st.success("โœ… No duplicate records detected.")
128
+
129
+ if st.button("Run Deduplication", disabled=(dup_count == 0)):
130
+ cleaned = current_working_df.drop_duplicates(subset=subset_param, keep=keep_strategy)
131
+ removed_count = len(current_working_df) - len(cleaned)
132
+ st.session_state.df = cleaned
133
+ st.session_state.last_action = f"Deduplication removed {removed_count} duplicate rows."
134
+ st.session_state.data_version += 1
135
+ st.rerun()
136
+
137
+ st.divider()
138
+
139
+ # ---------------------------------------------------------
140
+ # Dynamic Multi-Column Distinct Values Filter Section
141
+ # ---------------------------------------------------------
142
+ st.write("##### 2. Dynamic Value Filtering")
143
+ st.caption("Select columns to filter, then choose specific distinct values from each.")
144
+
145
+ # Step A: Select which columns the user wants to filter by
146
+ selected_filter_columns = st.multiselect(
147
+ "Select columns to add filters for:",
148
+ options=list(current_working_df.columns),
149
+ key=f"active_filter_cols_{st.session_state.data_version}"
150
+ )
151
+
152
+ filter_selections = {}
153
+ if selected_filter_columns:
154
+ # Dynamically generate input columns (up to 3 dropdowns per row)
155
+ num_cols = min(3, len(selected_filter_columns))
156
+ ui_cols = st.columns(num_cols)
157
+
158
+ for idx, col_name in enumerate(selected_filter_columns):
159
+ # Extract sorted distinct non-null values
160
+ distinct_values = sorted(current_working_df[col_name].dropna().unique().tolist())
161
+
162
+ with ui_cols[idx % num_cols]:
163
+ chosen_values = st.multiselect(
164
+ f"Values for '{col_name}':",
165
+ options=distinct_values,
166
+ key=f"val_filter_{col_name}_{st.session_state.data_version}",
167
+ placeholder="Select values to keep..."
168
+ )
169
+ if chosen_values:
170
+ filter_selections[col_name] = chosen_values
171
+
172
+ # Build dynamic boolean mask across all selected dropdowns
173
+ active_filtered_df = current_working_df.copy()
174
+ if filter_selections:
175
+ mask = pd.Series(True, index=current_working_df.index)
176
+ for col_name, values in filter_selections.items():
177
+ mask = mask & current_working_df[col_name].isin(values)
178
+ active_filtered_df = current_working_df[mask]
179
+
180
+ st.info(f"๐Ÿ”Ž Match found: **{len(active_filtered_df)}** out of **{len(current_working_df)}** rows.")
181
+
182
+ # Live preview before applying
183
+ with st.expander("๐Ÿ‘๏ธ Preview Filtered Rows Before Applying", expanded=True):
184
+ st.dataframe(active_filtered_df.head(50), use_container_width=True)
185
+
186
+ if st.button("โœ… Confirm & Apply Filter"):
187
+ st.session_state.df = active_filtered_df
188
+ st.session_state.last_action = f"Filtered across {len(filter_selections)} column(s): retained {len(active_filtered_df)} rows."
189
+ st.session_state.data_version += 1
190
+ st.rerun()
191
+
192
+ st.divider()
193
+
194
+ # =========================================================
195
+ # 3. PREVIEW 2: AFTER APPLYING (Updated Working Dataset)
196
+ # =========================================================
197
+ active_df = st.session_state.df
198
+
199
+ st.write(f"##### ๐Ÿ“Š Preview 2: After Applying ({len(active_df)} rows ร— {len(active_df.columns)} cols)")
200
+ if st.session_state.get("last_action"):
201
+ st.success(f"โšก **Status:** {st.session_state.last_action}")
202
+
203
+ st.dataframe(
204
+ active_df.head(100),
205
+ use_container_width=True,
206
+ key=f"preview_after_table_{st.session_state.data_version}"
207
+ )
208
+ st.caption(f"Showing first {min(100, len(active_df))} of {len(active_df)} rows.")
209
+
210
+ # =========================================================
211
+ # 4. DOWNLOAD PROCESSED DATA (CSV & EXCEL)
212
+ # =========================================================
213
+ st.write("##### ๐Ÿ“ฅ Download Processed Data")
214
+ dl_col1, dl_col2 = st.columns(2)
215
+
216
+ # 1. Download as CSV
217
+ with dl_col1:
218
+ csv_buf = active_df.to_csv(index=False).encode('utf-8')
219
+ st.download_button(
220
+ label="๐Ÿ“„ Download as CSV",
221
+ data=csv_buf,
222
+ file_name="oneprocess_processed.csv",
223
+ mime="text/csv",
224
+ use_container_width=True
225
+ )
226
+
227
+ # 2. Download as Excel (.xlsx)
228
+ with dl_col2:
229
+ import io
230
+ excel_buf = io.BytesIO()
231
+ with pd.ExcelWriter(excel_buf, engine="openpyxl") as writer:
232
+ active_df.to_excel(writer, index=False, sheet_name="ProcessedData")
233
+ excel_buf.seek(0)
234
+
235
+ st.download_button(
236
+ label="๐Ÿ“Š Download as Excel (.xlsx)",
237
+ data=excel_buf.getvalue(),
238
+ file_name="oneprocess_processed.xlsx",
239
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
240
+ use_container_width=True
241
+ )
242
+
243
+ else:
244
+ st.info("Upload a dataset in the sidebar first.")
245
+
246
+ # TAB 3: File Merger
247
+ with tabs[2]:
248
+ st.subheader("File Merging Tool")
249
+ st.write("Merge two files that share identical or overlapping column names.")
250
+ file1 = st.file_uploader("Upload First File", type=["csv", "xlsx"], key="m_file1")
251
+ file2 = st.file_uploader("Upload Second File", type=["csv", "xlsx"], key="m_file2")
252
+ merge_mode = st.radio("Merge Mode", ["concat (Vertical Stack)", "inner (Common Columns Only)"], horizontal=True)
253
+
254
+ if file1 and file2 and st.button("Merge Files"):
255
+ mode = "concat" if "concat" in merge_mode else "inner"
256
+ merged_df = DataProcessor.merge_files(file1, file2, how=mode)
257
+ st.success(f"Successfully merged! Total rows: {len(merged_df)}")
258
+ st.dataframe(merged_df.head(10), use_container_width=True)
259
+ st.download_button(
260
+ label="๐Ÿ“ฅ Download Merged File",
261
+ data=merged_df.to_csv(index=False).encode('utf-8'),
262
+ file_name="merged_output.csv",
263
+ mime="text/csv"
264
+ )
265
+
266
+ # TAB 4: Bar Graphs with Notes
267
+ with tabs[3]:
268
+ st.subheader("Bar Graph Analysis")
269
+
270
+ if st.session_state.get("df") is not None:
271
+ # Check if the active dataset was updated in Tab 2
272
+ current_data_version = st.session_state.get("data_version", 0)
273
+ if st.session_state.get("last_seen_data_version") != current_data_version:
274
+ st.session_state.chart_fig = None
275
+ st.session_state.chart_notes_saved = ""
276
+ st.session_state.last_seen_data_version = current_data_version
277
+
278
+ # Initialize chart state trackers
279
+ if "chart_fig" not in st.session_state:
280
+ st.session_state.chart_fig = None
281
+ if "chart_notes_saved" not in st.session_state:
282
+ st.session_state.chart_notes_saved = ""
283
+ if "chart_version" not in st.session_state:
284
+ st.session_state.chart_version = 0
285
+
286
+ # Display reset notification if user recently reset the chart
287
+ if st.session_state.get("show_filter_reset_msg"):
288
+ st.warning("โ„น๏ธ Chart reset! If you want to analyze the full original dataset, please reset the filtering in the **Clean & Transform** tab.")
289
+
290
+ df = st.session_state.df
291
+
292
+ # Controls & Dropdowns
293
+ c1, c2, c3 = st.columns(3)
294
+ with c1:
295
+ x_axis = st.selectbox(
296
+ "Category (X-axis)",
297
+ df.columns,
298
+ key=f"chart_x_{st.session_state.chart_version}"
299
+ )
300
+ with c2:
301
+ num_cols = [c for c in df.columns if pd.api.types.is_numeric_dtype(df[c])]
302
+ y_axis = st.selectbox(
303
+ "Metric (Y-axis)",
304
+ num_cols if num_cols else df.columns,
305
+ key=f"chart_y_{st.session_state.chart_version}"
306
+ )
307
+ with c3:
308
+ agg = st.selectbox(
309
+ "Aggregation",
310
+ ["sum", "mean", "count"],
311
+ key=f"chart_agg_{st.session_state.chart_version}"
312
+ )
313
+
314
+ chart_notes = st.text_area(
315
+ "Analysis / Observation Notes",
316
+ value=st.session_state.chart_notes_saved,
317
+ placeholder="Type key insights about this distribution...",
318
+ key=f"chart_notes_input_{st.session_state.chart_version}"
319
+ )
320
+
321
+ # Action Buttons: Generate & Reset
322
+ btn_col1, btn_col2, _ = st.columns([1.5, 1.5, 5])
323
+
324
+ with btn_col1:
325
+ if st.button("๐Ÿ“Š Generate Chart", type="primary", use_container_width=True):
326
+ # Clear the reminder message on new generation
327
+ st.session_state.show_filter_reset_msg = False
328
+
329
+ fig = GraphAnalyzer.create_bar_chart(
330
+ df,
331
+ x_col=x_axis,
332
+ y_col=y_axis,
333
+ aggregation=agg,
334
+ title=f"{agg.capitalize()} of {y_axis} by {x_axis}"
335
+ )
336
+ st.session_state.chart_fig = fig
337
+ st.session_state.chart_notes_saved = chart_notes
338
+ st.rerun()
339
+
340
+ with btn_col2:
341
+ if st.button("๐Ÿ”„ Reset Chart", use_container_width=True):
342
+ st.session_state.chart_fig = None
343
+ st.session_state.chart_notes_saved = ""
344
+ st.session_state.chart_version += 1
345
+ # Trigger the reminder message
346
+ st.session_state.show_filter_reset_msg = True
347
+ st.rerun()
348
+
349
+ # Display chart and notes if generated
350
+ if st.session_state.chart_fig is not None:
351
+ st.divider()
352
+ st.plotly_chart(st.session_state.chart_fig, use_container_width=True)
353
+ if st.session_state.chart_notes_saved:
354
+ st.info(f"**Section Notes:** {st.session_state.chart_notes_saved}")
355
+
356
+ else:
357
+ st.info("Upload data in the sidebar to generate charts.")
oneprocess/cli.py ADDED
@@ -0,0 +1,24 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+
5
+
6
+ def run():
7
+ """CLI launcher for the OneProcess Streamlit dashboard."""
8
+ print("\n" + "=" * 55)
9
+ print("OneProcess Data Processing & Analytics Toolkit")
10
+ print("Launching Data Processing Tool...")
11
+ print("=" * 55 + "\n")
12
+
13
+ # Locate app.py in the same directory as this cli.py
14
+ current_dir = os.path.dirname(os.path.abspath(__file__))
15
+ app_path = os.path.join(current_dir, "app.py")
16
+
17
+ try:
18
+ subprocess.run([sys.executable, "-m", "streamlit", "run", app_path])
19
+ except KeyboardInterrupt:
20
+ print("\nOneProcess stopped.")
21
+
22
+
23
+ if __name__ == "__main__":
24
+ run()
@@ -0,0 +1,4 @@
1
+ from oneprocess.core.processor import DataProcessor
2
+ from oneprocess.core.inspector import ColumnTypeIdentifier
3
+
4
+ __all__ = ["DataProcessor", "ColumnTypeIdentifier"]
@@ -0,0 +1,38 @@
1
+ import pandas as pd
2
+
3
+
4
+ class ColumnTypeIdentifier:
5
+ """Utility class to analyze and inspect DataFrame schemas, missing values, and data types."""
6
+
7
+ def __init__(self, df: pd.DataFrame):
8
+ self.df = df
9
+
10
+ def inspect(self) -> pd.DataFrame:
11
+ """Inspects data types, missing values, uniqueness, and samples for all columns.
12
+
13
+ Returns a DataFrame where:
14
+ - Columns (X-axis): Your dataset's column names
15
+ - Index (Y-axis): Metric names (Inferred Type, Missing Values, etc.)
16
+ """
17
+ metrics = {}
18
+ total_count = len(self.df)
19
+
20
+ for col in self.df.columns:
21
+ missing_count = int(self.df[col].isna().sum())
22
+ missing_pct = round((missing_count / total_count) * 100, 2) if total_count > 0 else 0.0
23
+ unique_count = int(self.df[col].nunique())
24
+ non_null_series = self.df[col].dropna()
25
+ sample_val = str(non_null_series.iloc[0]) if not non_null_series.empty else "None"
26
+
27
+ metrics[col] = {
28
+ "Inferred Type": str(self.df[col].dtype),
29
+ "Missing Values": missing_count,
30
+ "Missing %": f"{missing_pct}%",
31
+ "Unique Values": unique_count,
32
+ "Sample": sample_val
33
+ }
34
+
35
+ # Converting dict to DataFrame naturally places column names at top and metric keys on the Y-axis
36
+ result_df = pd.DataFrame(metrics)
37
+ result_df.index.name = "Metric"
38
+ return result_df
@@ -0,0 +1,105 @@
1
+ """
2
+ Core file processing, cleaning, schema inspection, and merging operations.
3
+ """
4
+ from typing import List, Optional, Union
5
+ import io
6
+ import pandas as pd
7
+
8
+
9
+ class DataProcessor:
10
+ def __init__(self, data: Union[str, pd.DataFrame, io.BytesIO], sheet_name: Optional[str] = None):
11
+ """
12
+ Initialize with a file path, BytesIO buffer (from Streamlit), or existing DataFrame.
13
+ """
14
+ if isinstance(data, pd.DataFrame):
15
+ self.df = data.copy()
16
+ elif isinstance(data, (str, io.BytesIO)):
17
+ self.df = self.read_file(data, sheet_name=sheet_name)
18
+ else:
19
+ raise ValueError("Unsupported data source. Provide a file path, buffer, or DataFrame.")
20
+
21
+ @staticmethod
22
+ def read_file(source: Union[str, io.BytesIO], sheet_name: Optional[str] = None) -> pd.DataFrame:
23
+ """Helper to read either CSV or Excel seamlessly."""
24
+ if isinstance(source, str):
25
+ if source.endswith((".xlsx", ".xls")):
26
+ return pd.read_excel(source, sheet_name=sheet_name or 0)
27
+ return pd.read_csv(source)
28
+ else:
29
+ # BytesIO stream from file uploaders
30
+ try:
31
+ return pd.read_csv(source)
32
+ except Exception:
33
+ source.seek(0)
34
+ return pd.read_excel(source, sheet_name=sheet_name or 0)
35
+
36
+ def identify_column_types(self) -> pd.DataFrame:
37
+ """
38
+ Inspect columns, detect data types, count nulls, and provide sample values.
39
+ """
40
+ summary = []
41
+ for col in self.df.columns:
42
+ series = self.df[col]
43
+ null_count = int(series.isnull().sum())
44
+ null_pct = round((null_count / len(self.df)) * 100, 2) if len(self.df) > 0 else 0
45
+
46
+ # Inferred logical category
47
+ dtype_str = str(series.dtype)
48
+ if pd.api.types.is_numeric_dtype(series):
49
+ category = "Numeric"
50
+ elif pd.api.types.is_datetime64_any_dtype(series):
51
+ category = "Datetime"
52
+ elif pd.api.types.is_bool_dtype(series):
53
+ category = "Boolean"
54
+ else:
55
+ category = "Categorical / Text"
56
+
57
+ sample_val = str(series.dropna().iloc[0]) if not series.dropna().empty else "None"
58
+
59
+ summary.append({
60
+ "Column Name": col,
61
+ "Inferred Category": category,
62
+ "Pandas Dtype": dtype_str,
63
+ "Missing Values": null_count,
64
+ "Missing (%)": f"{null_pct}%",
65
+ "Sample Value": sample_val
66
+ })
67
+ return pd.DataFrame(summary)
68
+
69
+ def deduplicate(self, subset: Optional[List[str]] = None, keep: str = "first") -> pd.DataFrame:
70
+ """Removes duplicate records based on optional primary key columns."""
71
+ initial_count = len(self.df)
72
+ self.df = self.df.drop_duplicates(subset=subset, keep=keep).reset_index(drop=True)
73
+ return self.df
74
+
75
+ def filter_data(self, query_string: str) -> pd.DataFrame:
76
+ """Filter dataset rows using standard pandas query syntax."""
77
+ self.df = self.df.query(query_string).reset_index(drop=True)
78
+ return self.df
79
+
80
+ def create_subset(self, columns: List[str], condition: Optional[str] = None) -> pd.DataFrame:
81
+ """Extracts specified columns and optional filtered rows into a new DataFrame."""
82
+ filtered_df = self.df.query(condition) if condition else self.df
83
+ return filtered_df[columns].copy().reset_index(drop=True)
84
+
85
+ @staticmethod
86
+ def merge_files(
87
+ first_source: Union[str, pd.DataFrame, io.BytesIO],
88
+ second_source: Union[str, pd.DataFrame, io.BytesIO],
89
+ how: str = "concat"
90
+ ) -> pd.DataFrame:
91
+ """
92
+ Merges two files or DataFrames having identical or compatible columns.
93
+ how: 'concat' (vertical stack) or 'inner' (only matching columns).
94
+ """
95
+ df1 = first_source if isinstance(first_source, pd.DataFrame) else DataProcessor.read_file(first_source)
96
+ df2 = second_source if isinstance(second_source, pd.DataFrame) else DataProcessor.read_file(second_source)
97
+
98
+ if how == "concat":
99
+ merged = pd.concat([df1, df2], ignore_index=True)
100
+ elif how == "inner":
101
+ common_cols = [c for c in df1.columns if c in df2.columns]
102
+ merged = pd.concat([df1[common_cols], df2[common_cols]], ignore_index=True)
103
+ else:
104
+ raise ValueError("Supported merge modes are 'concat' and 'inner'.")
105
+ return merged
@@ -0,0 +1,3 @@
1
+ from oneprocess.visuals.charts import GraphAnalyzer
2
+
3
+ __all__ = ["GraphAnalyzer"]
@@ -0,0 +1,31 @@
1
+ import pandas as pd
2
+ import plotly.express as px
3
+
4
+ class GraphAnalyzer:
5
+ @staticmethod
6
+ def create_bar_chart(
7
+ df: pd.DataFrame,
8
+ x_col: str,
9
+ y_col: str,
10
+ title: str = "Bar Chart",
11
+ aggregation: str = "sum"
12
+ ):
13
+ """Generates an interactive Plotly bar chart with aggregation."""
14
+ if aggregation == "sum":
15
+ grouped_df = df.groupby(x_col, as_index=False)[y_col].sum()
16
+ elif aggregation == "mean":
17
+ grouped_df = df.groupby(x_col, as_index=False)[y_col].mean()
18
+ elif aggregation == "count":
19
+ grouped_df = df.groupby(x_col, as_index=False)[y_col].count()
20
+ else:
21
+ grouped_df = df
22
+
23
+ fig = px.bar(
24
+ grouped_df,
25
+ x=x_col,
26
+ y=y_col,
27
+ title=title,
28
+ labels={x_col: x_col, y_col: f"{y_col} ({aggregation})"},
29
+ template="plotly_white"
30
+ )
31
+ return fig
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: oneprocess
3
+ Version: 0.1.0
4
+ Summary: A unified data processing, cleaning, and analytics toolkit with Streamlit support.
5
+ Author: Merwin Pinto
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: pandas>=2.0.0
11
+ Requires-Dist: numpy>=1.24.0
12
+ Requires-Dist: openpyxl>=3.1.0
13
+ Requires-Dist: xlsxwriter>=3.1.0
14
+ Requires-Dist: plotly>=5.18.0
15
+ Provides-Extra: streamlit
16
+ Requires-Dist: streamlit>=1.35.0; extra == "streamlit"
17
+ Dynamic: license-file
18
+
19
+ # OneProcess
20
+
21
+ [![PyPI version](https://img.shields.io/pypi/v/oneprocess.svg)](https://pypi.org/project/oneprocess/)
22
+ [![Python Versions](https://img.shields.io/pypi/pyversions/oneprocess.svg)](https://pypi.org/project/oneprocess/)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
24
+
25
+ **OneProcess** is a lightweight, unified data processing toolkit built for Data Analysts and Data Engineers. It eliminates the boilerplate code needed for everyday file wrangling, data cleaning, schema inspection, and exploratory data visualization.
26
+
27
+ ---
28
+
29
+ ## Key Capabilities
30
+
31
+ ### 1. Schema & Column Type Identification
32
+ - **Schema Mapping:** Inspect files to infer data types, calculate missing value percentages, and preview samples across all columns.
33
+ - **Transposed Inspection Layout:** View field properties with dataset columns organized horizontally across the top for easy side-by-side comparison.
34
+
35
+ ### 2. File Processing & Cleaning (CSV & Excel)
36
+ - **Deduplication Engine:** Identify duplicate entries with full row previews before dropping, configure first/last retention strategies, and target specific primary keys or the entire dataset.
37
+ - **Dynamic Multi-Column Filtering:** Automatically extract unique distinct values per column to filter records via intuitive dropdowns without writing manual queries.
38
+ - **Two-Stage Preview Workflow:** Inspect your raw data prior to changes, preview filtered matches in real time, and verify the resulting dataset before committing.
39
+ - **Dual Export Options:** Download cleaned and transformed datasets directly as CSV or Excel (`.xlsx`).
40
+
41
+ ### 3. File Merging
42
+ - **Schema-Aligned Merging:** Combine multiple CSV or Excel files sharing matching column structures into a consolidated dataset.
43
+
44
+ ### 4. Graph & Visual Analysis
45
+ - **Interactive Bar Charts:** Generate customizable Plotly bar charts with flexible aggregations (`sum`, `mean`, `count`) across categorical and numeric fields.
46
+ - **Insight Notes & Quick Reset:** Annotate charts with contextual observations and reset chart configurations instantly with a single click.
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ # Core library only
54
+ pip install oneprocess
55
+
56
+ # Complete installation with UI dashboard support
57
+ pip install oneprocess[streamlit]
58
+
59
+ # To run Code
60
+ python -m oneprocess.cli
@@ -0,0 +1,14 @@
1
+ oneprocess/__init__.py,sha256=JhgXFeVWfIMIU5HzcaMvGMc0CXDRJ2rC3MOh_iZPKUM,266
2
+ oneprocess/app.py,sha256=cq6LStsAWUBnZZQuC9SWwITCwKdjfQClM_Ahp1pX8fE,15768
3
+ oneprocess/cli.py,sha256=c3SJ5uM7tpfvZBnR_hdTcDmEU4XLc0Oq26VVVJsmAfI,663
4
+ oneprocess/core/__init__.py,sha256=opBSJPL3qaUK9tQsM-MgBt8NyxSgd6DDvyRjc4xAVLM,166
5
+ oneprocess/core/inspector.py,sha256=kbzYbWkaqIhLNZW6j2xAJDhUcfGpyneY6d5hDbdDw3o,1521
6
+ oneprocess/core/processor.py,sha256=ctTzADEKHyuHuwK1bMNqDCDTuZXTK9bGfKZ9p9w8VgE,4621
7
+ oneprocess/visuals/__init__.py,sha256=JhNF-WJHz0yQluj2mmKrNnodgcEqe3NwRiT84h7HxDM,82
8
+ oneprocess/visuals/charts.py,sha256=eqaRocNl4HIPHVSFu_4RuLK-HEfd730VCjRx5WBFJ6I,973
9
+ oneprocess-0.1.0.dist-info/licenses/LICENSE,sha256=QT4LTVe-Yryc_10VzbWncDvo0rqKNqcEWZIhNGqcHik,1090
10
+ oneprocess-0.1.0.dist-info/METADATA,sha256=DR6vModv_qc9CYQt0R2wk7TUZqGslAiKUqKbgm7kQEg,2889
11
+ oneprocess-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
12
+ oneprocess-0.1.0.dist-info/entry_points.txt,sha256=8TXav0JU2G3jLMIZbUH_jhDB1og4h9nB6jkRKtMhNLk,86
13
+ oneprocess-0.1.0.dist-info/top_level.txt,sha256=_7i14ITPjZGnmUlIBBkDpMpdQ33QHHQCjMw6ZQav4hU,11
14
+ oneprocess-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ oneprocess = oneprocess.cli:run
3
+ oneprocess-app = oneprocess.cli:run
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Merwin Pinto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ oneprocess