oneprocess 0.1.0__tar.gz
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-0.1.0/LICENSE +21 -0
- oneprocess-0.1.0/PKG-INFO +60 -0
- oneprocess-0.1.0/README.md +42 -0
- oneprocess-0.1.0/oneprocess/__init__.py +11 -0
- oneprocess-0.1.0/oneprocess/app.py +357 -0
- oneprocess-0.1.0/oneprocess/cli.py +24 -0
- oneprocess-0.1.0/oneprocess/core/__init__.py +4 -0
- oneprocess-0.1.0/oneprocess/core/inspector.py +38 -0
- oneprocess-0.1.0/oneprocess/core/processor.py +105 -0
- oneprocess-0.1.0/oneprocess/visuals/__init__.py +3 -0
- oneprocess-0.1.0/oneprocess/visuals/charts.py +31 -0
- oneprocess-0.1.0/oneprocess.egg-info/PKG-INFO +60 -0
- oneprocess-0.1.0/oneprocess.egg-info/SOURCES.txt +17 -0
- oneprocess-0.1.0/oneprocess.egg-info/dependency_links.txt +1 -0
- oneprocess-0.1.0/oneprocess.egg-info/entry_points.txt +3 -0
- oneprocess-0.1.0/oneprocess.egg-info/requires.txt +8 -0
- oneprocess-0.1.0/oneprocess.egg-info/top_level.txt +1 -0
- oneprocess-0.1.0/pyproject.toml +35 -0
- oneprocess-0.1.0/setup.cfg +4 -0
oneprocess-0.1.0/LICENSE
ADDED
|
@@ -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,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
|
+
[](https://pypi.org/project/oneprocess/)
|
|
22
|
+
[](https://pypi.org/project/oneprocess/)
|
|
23
|
+
[](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,42 @@
|
|
|
1
|
+
# OneProcess
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/oneprocess/)
|
|
4
|
+
[](https://pypi.org/project/oneprocess/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
**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.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Key Capabilities
|
|
12
|
+
|
|
13
|
+
### 1. Schema & Column Type Identification
|
|
14
|
+
- **Schema Mapping:** Inspect files to infer data types, calculate missing value percentages, and preview samples across all columns.
|
|
15
|
+
- **Transposed Inspection Layout:** View field properties with dataset columns organized horizontally across the top for easy side-by-side comparison.
|
|
16
|
+
|
|
17
|
+
### 2. File Processing & Cleaning (CSV & Excel)
|
|
18
|
+
- **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.
|
|
19
|
+
- **Dynamic Multi-Column Filtering:** Automatically extract unique distinct values per column to filter records via intuitive dropdowns without writing manual queries.
|
|
20
|
+
- **Two-Stage Preview Workflow:** Inspect your raw data prior to changes, preview filtered matches in real time, and verify the resulting dataset before committing.
|
|
21
|
+
- **Dual Export Options:** Download cleaned and transformed datasets directly as CSV or Excel (`.xlsx`).
|
|
22
|
+
|
|
23
|
+
### 3. File Merging
|
|
24
|
+
- **Schema-Aligned Merging:** Combine multiple CSV or Excel files sharing matching column structures into a consolidated dataset.
|
|
25
|
+
|
|
26
|
+
### 4. Graph & Visual Analysis
|
|
27
|
+
- **Interactive Bar Charts:** Generate customizable Plotly bar charts with flexible aggregations (`sum`, `mean`, `count`) across categorical and numeric fields.
|
|
28
|
+
- **Insight Notes & Quick Reset:** Annotate charts with contextual observations and reset chart configurations instantly with a single click.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Core library only
|
|
36
|
+
pip install oneprocess
|
|
37
|
+
|
|
38
|
+
# Complete installation with UI dashboard support
|
|
39
|
+
pip install oneprocess[streamlit]
|
|
40
|
+
|
|
41
|
+
# To run Code
|
|
42
|
+
python -m oneprocess.cli
|
|
@@ -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
|
+
]
|
|
@@ -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.")
|
|
@@ -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,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,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
|
+
[](https://pypi.org/project/oneprocess/)
|
|
22
|
+
[](https://pypi.org/project/oneprocess/)
|
|
23
|
+
[](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,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
oneprocess/__init__.py
|
|
5
|
+
oneprocess/app.py
|
|
6
|
+
oneprocess/cli.py
|
|
7
|
+
oneprocess.egg-info/PKG-INFO
|
|
8
|
+
oneprocess.egg-info/SOURCES.txt
|
|
9
|
+
oneprocess.egg-info/dependency_links.txt
|
|
10
|
+
oneprocess.egg-info/entry_points.txt
|
|
11
|
+
oneprocess.egg-info/requires.txt
|
|
12
|
+
oneprocess.egg-info/top_level.txt
|
|
13
|
+
oneprocess/core/__init__.py
|
|
14
|
+
oneprocess/core/inspector.py
|
|
15
|
+
oneprocess/core/processor.py
|
|
16
|
+
oneprocess/visuals/__init__.py
|
|
17
|
+
oneprocess/visuals/charts.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
oneprocess
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "oneprocess"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A unified data processing, cleaning, and analytics toolkit with Streamlit support."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Merwin Pinto" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pandas>=2.0.0",
|
|
17
|
+
"numpy>=1.24.0",
|
|
18
|
+
"openpyxl>=3.1.0",
|
|
19
|
+
"xlsxwriter>=3.1.0",
|
|
20
|
+
"plotly>=5.18.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
streamlit = [
|
|
25
|
+
"streamlit>=1.35.0"
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
oneprocess = "oneprocess.cli:run"
|
|
30
|
+
oneprocess-app = "oneprocess.cli:run"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["."]
|
|
34
|
+
include = ["oneprocess*"]
|
|
35
|
+
exclude = ["oneprocessvenv*", "venv*", "tests*"]
|