leadingbridge commited on
Commit
57d62a1
·
verified ·
1 Parent(s): a5017b2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +21 -74
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import gradio as gr
2
  from openpyxl import load_workbook
3
  from openpyxl.worksheet.worksheet import Worksheet
 
4
  from huggingface_hub import hf_hub_download
5
  import tempfile
6
  import os, re
@@ -77,12 +78,10 @@ def process(input_file):
77
  col_pov = header_map["product option value"]
78
  col_qty = header_map["quantity"]
79
 
80
- # Capture the entire header row & all row values so unmatched lines can be copied verbatim
81
  in_max_col = ws_in.max_column
82
  header_values = [ws_in.cell(row=header_row_idx, column=c).value for c in range(1, in_max_col + 1)]
83
 
84
- # Pre-collect all entries (we'll decide matched vs unmatched after loading the template)
85
- entries = [] # list of dicts: {sku, power_norm, qty, row_values}
86
  rows_scanned = 0
87
  for r in range(header_row_idx + 1, ws_in.max_row + 1):
88
  row_values = [ws_in.cell(row=r, column=c).value for c in range(1, in_max_col + 1)]
@@ -95,7 +94,6 @@ def process(input_file):
95
  rows_scanned += 1
96
 
97
  power = _normalize_power(pov)
98
- # robust int conversion
99
  try:
100
  q = int(qty) if qty is not None and str(qty).strip() != "" else 0
101
  except Exception:
@@ -111,15 +109,11 @@ def process(input_file):
111
  "row_values": row_values
112
  })
113
 
114
- # --- OUTPUT: load template ---
115
  template_path = _download_template()
116
  wb_out = load_workbook(template_path)
117
  ws_out = wb_out.active
118
 
119
- # Find:
120
- # (A) "MY SKU" column to build SKU->row map (instead of "SKU")
121
- # (B) power label row (text like 0.00, -1.25)
122
- # (C) triplet label row (000, 125, 400) as fallback
123
  mysku_header_row = None
124
  mysku_col_idx = None
125
  power_label_row = None
@@ -129,14 +123,10 @@ def process(input_file):
129
 
130
  for r in range(1, 11):
131
  row_vals = [ws_out.cell(row=r, column=c).value for c in range(1, ws_out.max_column + 1)]
132
-
133
- # (A) "MY SKU" detection
134
  for c, v in enumerate(row_vals, start=1):
135
  if isinstance(v, str) and v.strip().lower() == "my sku":
136
  mysku_header_row = r
137
  mysku_col_idx = c
138
-
139
- # (B) textual power labels
140
  labels = {}
141
  for c, v in enumerate(row_vals, start=1):
142
  if isinstance(v, str):
@@ -146,8 +136,6 @@ def process(input_file):
146
  if len(labels) >= 5 and power_label_row is None:
147
  power_label_row = r
148
  power_col_map = labels
149
-
150
- # (C) numeric triplets
151
  trip = {}
152
  for c, v in enumerate(row_vals, start=1):
153
  if isinstance(v, str) and re.fullmatch(r"\d{2,3}", v.strip()):
@@ -157,11 +145,10 @@ def process(input_file):
157
  triplet_col_map = trip
158
 
159
  if mysku_header_row is None or mysku_col_idx is None:
160
- raise ValueError("Could not find the 'MY SKU' header in the template (looked in rows 1–10).")
161
  if not (power_label_row or triplet_row):
162
- raise ValueError("Could not find power-column headers in the template (looked in rows 1–10).")
163
 
164
- # Build SKU -> row map using the "MY SKU" column
165
  sku_to_row = {}
166
  for r in range(mysku_header_row + 1, ws_out.max_row + 1):
167
  val = ws_out.cell(row=r, column=mysku_col_idx).value
@@ -169,63 +156,37 @@ def process(input_file):
169
  continue
170
  sku_to_row[str(val).strip()] = r
171
 
172
- # Optional top-area "MY SKU" summary (distinct from the table header row)
173
- summary_cell = None
174
- for r in range(1, 11):
175
- if r == mysku_header_row:
176
- continue
177
- for c in range(1, ws_out.max_column + 1):
178
- v = ws_out.cell(row=r, column=c).value
179
- if isinstance(v, str) and v.strip().lower() == "my sku":
180
- summary_cell = (r, c + 1)
181
- break
182
- if summary_cell:
183
- break
184
-
185
- # Classify entries: matched vs unmatched (line-by-line), and aggregate matched ones
186
- agg = defaultdict(int) # (sku, power) -> summed qty
187
- unmatched_rows = [] # list of row_values (verbatim from input)
188
 
189
  for rec in entries:
190
  sku, power, qty = rec["sku"], rec["power"], rec["qty"]
191
- # Invalid minimal fields => treat as unmatched copy-through
192
  if not sku or qty <= 0 or power is None:
193
  unmatched_rows.append(rec["row_values"])
194
  continue
195
-
196
  row_idx = sku_to_row.get(sku)
197
  if row_idx is None:
198
  unmatched_rows.append(rec["row_values"])
199
  continue
200
-
201
  col_idx = power_col_map.get(power) if power_col_map else None
202
  if col_idx is None and triplet_col_map:
203
  key = _power_to_triplet_digits(power)
204
  col_idx = triplet_col_map.get(key)
205
-
206
  if col_idx is None:
207
  unmatched_rows.append(rec["row_values"])
208
  continue
209
-
210
- # It's a match — add to aggregation
211
  agg[(sku, power)] += qty
212
 
213
- # Write aggregated matches to the template grid
214
  written_count = 0
215
- missing_skus = set()
216
- missing_powers = set()
217
-
218
  for (sku, power), qty in agg.items():
219
  row_idx = sku_to_row.get(sku)
220
  if row_idx is None:
221
- missing_skus.add(sku)
222
  continue
223
  col_idx = power_col_map.get(power) if power_col_map else None
224
  if col_idx is None and triplet_col_map:
225
  key = _power_to_triplet_digits(power)
226
  col_idx = triplet_col_map.get(key)
227
  if col_idx is None:
228
- missing_powers.add(power)
229
  continue
230
  current = ws_out.cell(row=row_idx, column=col_idx).value
231
  try:
@@ -238,39 +199,30 @@ def process(input_file):
238
  ws_out.cell(row=row_idx, column=col_idx).value = current_val + int(qty)
239
  written_count += 1
240
 
241
- # Fill the optional top-area summary of unique SKUs
242
- if summary_cell and (agg or unmatched_rows):
243
- unique_skus = sorted({sku for (sku, _) in agg.keys()})
244
- # Also include SKUs from unmatched rows where available
245
- try:
246
- # Find the index of "SKU" in the input header to extract from unmatched rows
247
- sku_header_idx = next((i for i, v in enumerate(header_values) if isinstance(v, str) and v.strip().lower() == "sku"), None)
248
- if sku_header_idx is not None:
249
- for rv in unmatched_rows:
250
- if sku_header_idx < len(rv) and rv[sku_header_idx]:
251
- unique_skus.append(str(rv[sku_header_idx]).strip())
252
- except Exception:
253
- pass
254
- if unique_skus:
255
- ws_out.cell(row=summary_cell[0], column=summary_cell[1]).value = ", ".join(sorted(set(unique_skus)))
256
-
257
- # --- Create/replace the "additional order" sheet with unmatched rows copied verbatim ---
258
  sheet_name = "additional order"
259
  if sheet_name in wb_out.sheetnames:
260
- # remove and recreate to avoid remnants
261
- ws_old = wb_out[sheet_name]
262
- wb_out.remove(ws_old)
263
  ws_extra = wb_out.create_sheet(title=sheet_name)
264
 
265
  # Write header
266
  for c, val in enumerate(header_values, start=1):
267
  ws_extra.cell(row=1, column=c).value = val
268
-
269
- # Write unmatched lines
270
  for i, row_vals in enumerate(unmatched_rows, start=2):
271
  for c, val in enumerate(row_vals, start=1):
272
  ws_extra.cell(row=i, column=c).value = val
273
 
 
 
 
 
 
 
 
 
 
 
274
  # Save output
275
  tmpdir = tempfile.mkdtemp()
276
  out_path = os.path.join(tmpdir, "AMMU-order-form-FILLED.xlsx")
@@ -282,12 +234,7 @@ def process(input_file):
282
  f"Entries written into template: {written_count}",
283
  f"Unmatched rows copied to '{sheet_name}': {len(unmatched_rows)}",
284
  ]
285
- if missing_skus:
286
- log_lines.append(f"⚠️ SKUs missing during aggregate write ({len(missing_skus)}): {', '.join(sorted(missing_skus))}")
287
- if missing_powers:
288
- log_lines.append(f"⚠️ Powers missing during aggregate write ({len(missing_powers)}): {', '.join(sorted(missing_powers))}")
289
  log = "\n".join(log_lines)
290
-
291
  return out_path, log
292
 
293
  except Exception as e:
@@ -299,7 +246,7 @@ with gr.Blocks(title="AMMU Order Form Filler") as demo:
299
  "• Uses **MY SKU** column to map rows\n"
300
  "• Matches power columns (text like `-1.25` or fallback triplets like `125`)\n"
301
  "• Aggregates quantities for matched lines\n"
302
- "• Copies **unmatched lines** to a new sheet **`additional order`** with headers"
303
  )
304
  with gr.Row():
305
  in_file = gr.File(label="Upload input Excel (.xlsx)", file_types=[".xlsx"])
 
1
  import gradio as gr
2
  from openpyxl import load_workbook
3
  from openpyxl.worksheet.worksheet import Worksheet
4
+ from openpyxl.utils import get_column_letter
5
  from huggingface_hub import hf_hub_download
6
  import tempfile
7
  import os, re
 
78
  col_pov = header_map["product option value"]
79
  col_qty = header_map["quantity"]
80
 
 
81
  in_max_col = ws_in.max_column
82
  header_values = [ws_in.cell(row=header_row_idx, column=c).value for c in range(1, in_max_col + 1)]
83
 
84
+ entries = []
 
85
  rows_scanned = 0
86
  for r in range(header_row_idx + 1, ws_in.max_row + 1):
87
  row_values = [ws_in.cell(row=r, column=c).value for c in range(1, in_max_col + 1)]
 
94
  rows_scanned += 1
95
 
96
  power = _normalize_power(pov)
 
97
  try:
98
  q = int(qty) if qty is not None and str(qty).strip() != "" else 0
99
  except Exception:
 
109
  "row_values": row_values
110
  })
111
 
112
+ # --- OUTPUT template ---
113
  template_path = _download_template()
114
  wb_out = load_workbook(template_path)
115
  ws_out = wb_out.active
116
 
 
 
 
 
117
  mysku_header_row = None
118
  mysku_col_idx = None
119
  power_label_row = None
 
123
 
124
  for r in range(1, 11):
125
  row_vals = [ws_out.cell(row=r, column=c).value for c in range(1, ws_out.max_column + 1)]
 
 
126
  for c, v in enumerate(row_vals, start=1):
127
  if isinstance(v, str) and v.strip().lower() == "my sku":
128
  mysku_header_row = r
129
  mysku_col_idx = c
 
 
130
  labels = {}
131
  for c, v in enumerate(row_vals, start=1):
132
  if isinstance(v, str):
 
136
  if len(labels) >= 5 and power_label_row is None:
137
  power_label_row = r
138
  power_col_map = labels
 
 
139
  trip = {}
140
  for c, v in enumerate(row_vals, start=1):
141
  if isinstance(v, str) and re.fullmatch(r"\d{2,3}", v.strip()):
 
145
  triplet_col_map = trip
146
 
147
  if mysku_header_row is None or mysku_col_idx is None:
148
+ raise ValueError("Could not find the 'MY SKU' header in the template.")
149
  if not (power_label_row or triplet_row):
150
+ raise ValueError("Could not find power-column headers in the template.")
151
 
 
152
  sku_to_row = {}
153
  for r in range(mysku_header_row + 1, ws_out.max_row + 1):
154
  val = ws_out.cell(row=r, column=mysku_col_idx).value
 
156
  continue
157
  sku_to_row[str(val).strip()] = r
158
 
159
+ agg = defaultdict(int)
160
+ unmatched_rows = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  for rec in entries:
163
  sku, power, qty = rec["sku"], rec["power"], rec["qty"]
 
164
  if not sku or qty <= 0 or power is None:
165
  unmatched_rows.append(rec["row_values"])
166
  continue
 
167
  row_idx = sku_to_row.get(sku)
168
  if row_idx is None:
169
  unmatched_rows.append(rec["row_values"])
170
  continue
 
171
  col_idx = power_col_map.get(power) if power_col_map else None
172
  if col_idx is None and triplet_col_map:
173
  key = _power_to_triplet_digits(power)
174
  col_idx = triplet_col_map.get(key)
 
175
  if col_idx is None:
176
  unmatched_rows.append(rec["row_values"])
177
  continue
 
 
178
  agg[(sku, power)] += qty
179
 
 
180
  written_count = 0
 
 
 
181
  for (sku, power), qty in agg.items():
182
  row_idx = sku_to_row.get(sku)
183
  if row_idx is None:
 
184
  continue
185
  col_idx = power_col_map.get(power) if power_col_map else None
186
  if col_idx is None and triplet_col_map:
187
  key = _power_to_triplet_digits(power)
188
  col_idx = triplet_col_map.get(key)
189
  if col_idx is None:
 
190
  continue
191
  current = ws_out.cell(row=row_idx, column=col_idx).value
192
  try:
 
199
  ws_out.cell(row=row_idx, column=col_idx).value = current_val + int(qty)
200
  written_count += 1
201
 
202
+ # --- Create the "additional order" tab ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  sheet_name = "additional order"
204
  if sheet_name in wb_out.sheetnames:
205
+ wb_out.remove(wb_out[sheet_name])
 
 
206
  ws_extra = wb_out.create_sheet(title=sheet_name)
207
 
208
  # Write header
209
  for c, val in enumerate(header_values, start=1):
210
  ws_extra.cell(row=1, column=c).value = val
211
+ # Write unmatched rows
 
212
  for i, row_vals in enumerate(unmatched_rows, start=2):
213
  for c, val in enumerate(row_vals, start=1):
214
  ws_extra.cell(row=i, column=c).value = val
215
 
216
+ # --- Auto adjust column widths ---
217
+ for c in range(1, in_max_col + 1):
218
+ max_len = 0
219
+ col_letter = get_column_letter(c)
220
+ for r in range(1, len(unmatched_rows) + 2): # +1 header
221
+ val = ws_extra.cell(row=r, column=c).value
222
+ if val is not None:
223
+ max_len = max(max_len, len(str(val)))
224
+ ws_extra.column_dimensions[col_letter].width = max_len + 2 # padding
225
+
226
  # Save output
227
  tmpdir = tempfile.mkdtemp()
228
  out_path = os.path.join(tmpdir, "AMMU-order-form-FILLED.xlsx")
 
234
  f"Entries written into template: {written_count}",
235
  f"Unmatched rows copied to '{sheet_name}': {len(unmatched_rows)}",
236
  ]
 
 
 
 
237
  log = "\n".join(log_lines)
 
238
  return out_path, log
239
 
240
  except Exception as e:
 
246
  "• Uses **MY SKU** column to map rows\n"
247
  "• Matches power columns (text like `-1.25` or fallback triplets like `125`)\n"
248
  "• Aggregates quantities for matched lines\n"
249
+ "• Copies **unmatched lines** to new sheet **`additional order`**, with auto column width"
250
  )
251
  with gr.Row():
252
  in_file = gr.File(label="Upload input Excel (.xlsx)", file_types=[".xlsx"])