Update app.py
Browse files
app.py
CHANGED
@@ -1,26 +1,20 @@
|
|
1 |
import pandas as pd
|
2 |
-
import openpyxl
|
3 |
import gradio as gr
|
4 |
-
import io
|
5 |
-
import base64
|
6 |
-
from datetime import datetime
|
7 |
|
8 |
def convert_schedule(file_path, direction):
|
9 |
if file_path is None:
|
10 |
-
return pd.DataFrame({'
|
11 |
|
12 |
try:
|
13 |
-
#
|
14 |
raw = pd.read_excel(file_path, header=None)
|
15 |
header1 = raw.iloc[0, 1:].astype(object)
|
16 |
header2 = raw.iloc[1, 1:].astype(object)
|
17 |
|
18 |
-
# Decide which header row to use: prefer second if fully populated
|
19 |
if header2.notna().all() and not header2.str.startswith('Unnamed').any():
|
20 |
days = header2.tolist()
|
21 |
data_start = 2
|
22 |
else:
|
23 |
-
# Forward-fill merged first-row headers
|
24 |
days = []
|
25 |
last = None
|
26 |
for val in header1:
|
@@ -31,7 +25,6 @@ def convert_schedule(file_path, direction):
|
|
31 |
days.append(last)
|
32 |
data_start = 1
|
33 |
|
34 |
-
# 2. Load actual data using resolved day columns
|
35 |
df = pd.read_excel(
|
36 |
file_path,
|
37 |
header=data_start,
|
@@ -39,14 +32,10 @@ def convert_schedule(file_path, direction):
|
|
39 |
usecols=[0] + list(range(1, len(days) + 1))
|
40 |
)
|
41 |
df.columns = [str(day) for day in days]
|
42 |
-
|
43 |
-
# 3. Retain original day column order
|
44 |
day_cols = list(df.columns)
|
45 |
|
46 |
-
# 4. Build assignment mapping via explicit iteration
|
47 |
assignments = {}
|
48 |
if direction == 'A to B':
|
49 |
-
# Models in rows β Texters as rows
|
50 |
for model in df.index.astype(str):
|
51 |
for day in day_cols:
|
52 |
cell = df.at[model, day]
|
@@ -71,7 +60,6 @@ def convert_schedule(file_path, direction):
|
|
71 |
models = days_map.get(day, [])
|
72 |
result.at[texter, day] = ', '.join(models) if models else 'OFF'
|
73 |
else:
|
74 |
-
# Texters in rows β Models as rows
|
75 |
for texter in df.index.astype(str):
|
76 |
for day in day_cols:
|
77 |
cell = df.at[texter, day]
|
@@ -96,98 +84,44 @@ def convert_schedule(file_path, direction):
|
|
96 |
texters = days_map.get(day, [])
|
97 |
result.at[model, day] = ', '.join(texters) if texters else 'OFF'
|
98 |
|
99 |
-
# 5. Cleanup axis names
|
100 |
result.index.name = None
|
101 |
result.columns.name = None
|
102 |
-
|
103 |
-
# For display
|
104 |
display_df = result.reset_index().rename(columns={'index': first_col_name})
|
105 |
-
|
106 |
-
# 6. Create downloadable file using in-memory approach
|
107 |
-
result_clean = result.copy().fillna('OFF')
|
108 |
|
109 |
-
#
|
110 |
-
|
111 |
-
result_clean[col] = result_clean[col].astype(str)
|
112 |
-
|
113 |
-
# Create CSV file in memory (more reliable than Excel)
|
114 |
download_df = result_clean.reset_index().rename(columns={'index': first_col_name})
|
|
|
115 |
|
116 |
-
|
117 |
-
csv_buffer = io.StringIO()
|
118 |
-
download_df.to_csv(csv_buffer, index=False)
|
119 |
-
csv_content = csv_buffer.getvalue()
|
120 |
-
|
121 |
-
# Create filename with timestamp
|
122 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
123 |
-
filename = f"converted_schedule_{timestamp}.csv"
|
124 |
-
|
125 |
-
# Return the CSV content for download
|
126 |
-
return display_df, (csv_content.encode('utf-8'), filename)
|
127 |
|
128 |
except Exception as e:
|
129 |
-
|
130 |
-
|
131 |
-
error_df = pd.DataFrame({'Error': [error_msg]})
|
132 |
-
return error_df, None
|
133 |
|
134 |
-
#
|
135 |
-
def process_and_download(file_path, direction):
|
136 |
-
display_result, download_data = convert_schedule(file_path, direction)
|
137 |
-
|
138 |
-
if download_data is None:
|
139 |
-
return display_result, None
|
140 |
-
|
141 |
-
# Create a temporary file that Gradio can serve
|
142 |
-
import tempfile
|
143 |
-
import os
|
144 |
-
|
145 |
-
excel_content, filename = download_data
|
146 |
-
|
147 |
-
# Save to a temporary file in the system temp directory
|
148 |
-
temp_dir = tempfile.gettempdir()
|
149 |
-
temp_path = os.path.join(temp_dir, filename)
|
150 |
-
|
151 |
-
# Write CSV content
|
152 |
-
with open(temp_path, 'w', encoding='utf-8') as f:
|
153 |
-
f.write(excel_content.decode('utf-8'))
|
154 |
-
|
155 |
-
return display_result, temp_path
|
156 |
-
|
157 |
-
# Create the interface
|
158 |
iface = gr.Interface(
|
159 |
-
fn=
|
160 |
inputs=[
|
161 |
-
gr.File(
|
162 |
-
|
163 |
-
file_count='single',
|
164 |
-
file_types=['.xlsx', '.xls']
|
165 |
-
),
|
166 |
-
gr.Radio(
|
167 |
-
['A to B', 'B to A'],
|
168 |
-
label='Convert Direction',
|
169 |
-
value='A to B',
|
170 |
-
info='A to B: ModelsβTexters, B to A: TextersβModels'
|
171 |
-
)
|
172 |
],
|
173 |
outputs=[
|
174 |
-
gr.Dataframe(label='Converted Schedule (Preview)'
|
175 |
-
gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
],
|
177 |
-
title='π
|
178 |
-
description=
|
179 |
-
'**How to use:**\n'
|
180 |
-
'1. Upload your Excel file with a 7-day schedule\n'
|
181 |
-
'2. Choose conversion direction\n'
|
182 |
-
'3. Preview the result and download the converted file\n\n'
|
183 |
-
'*Supports merged headers and handles Models β Texters conversion*'
|
184 |
-
),
|
185 |
flagging_mode='never'
|
186 |
)
|
187 |
|
188 |
if __name__ == "__main__":
|
189 |
-
iface.launch(
|
190 |
-
server_name='0.0.0.0',
|
191 |
-
server_port=7860,
|
192 |
-
share=False
|
193 |
-
)
|
|
|
1 |
import pandas as pd
|
|
|
2 |
import gradio as gr
|
|
|
|
|
|
|
3 |
|
4 |
def convert_schedule(file_path, direction):
|
5 |
if file_path is None:
|
6 |
+
return pd.DataFrame({'Message': ['Please upload a file']}), ""
|
7 |
|
8 |
try:
|
9 |
+
# Same conversion logic as before
|
10 |
raw = pd.read_excel(file_path, header=None)
|
11 |
header1 = raw.iloc[0, 1:].astype(object)
|
12 |
header2 = raw.iloc[1, 1:].astype(object)
|
13 |
|
|
|
14 |
if header2.notna().all() and not header2.str.startswith('Unnamed').any():
|
15 |
days = header2.tolist()
|
16 |
data_start = 2
|
17 |
else:
|
|
|
18 |
days = []
|
19 |
last = None
|
20 |
for val in header1:
|
|
|
25 |
days.append(last)
|
26 |
data_start = 1
|
27 |
|
|
|
28 |
df = pd.read_excel(
|
29 |
file_path,
|
30 |
header=data_start,
|
|
|
32 |
usecols=[0] + list(range(1, len(days) + 1))
|
33 |
)
|
34 |
df.columns = [str(day) for day in days]
|
|
|
|
|
35 |
day_cols = list(df.columns)
|
36 |
|
|
|
37 |
assignments = {}
|
38 |
if direction == 'A to B':
|
|
|
39 |
for model in df.index.astype(str):
|
40 |
for day in day_cols:
|
41 |
cell = df.at[model, day]
|
|
|
60 |
models = days_map.get(day, [])
|
61 |
result.at[texter, day] = ', '.join(models) if models else 'OFF'
|
62 |
else:
|
|
|
63 |
for texter in df.index.astype(str):
|
64 |
for day in day_cols:
|
65 |
cell = df.at[texter, day]
|
|
|
84 |
texters = days_map.get(day, [])
|
85 |
result.at[model, day] = ', '.join(texters) if texters else 'OFF'
|
86 |
|
|
|
87 |
result.index.name = None
|
88 |
result.columns.name = None
|
89 |
+
|
90 |
+
# For display
|
91 |
display_df = result.reset_index().rename(columns={'index': first_col_name})
|
|
|
|
|
|
|
92 |
|
93 |
+
# Create CSV text for copying
|
94 |
+
result_clean = result.copy().fillna('OFF')
|
|
|
|
|
|
|
95 |
download_df = result_clean.reset_index().rename(columns={'index': first_col_name})
|
96 |
+
csv_text = download_df.to_csv(index=False)
|
97 |
|
98 |
+
return display_df, csv_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
|
100 |
except Exception as e:
|
101 |
+
error_df = pd.DataFrame({'Error': [f"Error: {str(e)}"]})
|
102 |
+
return error_df, f"Error: {str(e)}"
|
|
|
|
|
103 |
|
104 |
+
# Interface with copy-paste text output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
iface = gr.Interface(
|
106 |
+
fn=convert_schedule,
|
107 |
inputs=[
|
108 |
+
gr.File(label='Upload Weekly Schedule (.xlsx)', file_types=['.xlsx', '.xls']),
|
109 |
+
gr.Radio(['A to B', 'B to A'], label='Convert Direction', value='A to B')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
],
|
111 |
outputs=[
|
112 |
+
gr.Dataframe(label='Converted Schedule (Preview)'),
|
113 |
+
gr.Textbox(
|
114 |
+
label='CSV Data (Copy This Text)',
|
115 |
+
lines=20,
|
116 |
+
max_lines=50,
|
117 |
+
show_copy_button=True,
|
118 |
+
info="Copy this text and paste it into Excel or save as .csv file"
|
119 |
+
)
|
120 |
],
|
121 |
+
title='π Schedule Converter - Copy/Paste Version',
|
122 |
+
description='Upload β Convert β Copy the CSV text β Paste into Excel or save as .csv',
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
flagging_mode='never'
|
124 |
)
|
125 |
|
126 |
if __name__ == "__main__":
|
127 |
+
iface.launch(server_name='0.0.0.0', server_port=7860)
|
|
|
|
|
|
|
|