leadingbridge commited on
Commit
a5017b2
·
verified ·
1 Parent(s): 72933fe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -25
app.py CHANGED
@@ -77,10 +77,15 @@ def process(input_file):
77
  col_pov = header_map["product option value"]
78
  col_qty = header_map["quantity"]
79
 
80
- # --- Aggregate quantities by (SKU, power) ---
81
- agg = defaultdict(int)
 
 
 
 
82
  rows_scanned = 0
83
  for r in range(header_row_idx + 1, ws_in.max_row + 1):
 
84
  sku = ws_in.cell(row=r, column=col_sku).value
85
  pov = ws_in.cell(row=r, column=col_pov).value
86
  qty = ws_in.cell(row=r, column=col_qty).value
@@ -90,6 +95,7 @@ def process(input_file):
90
  rows_scanned += 1
91
 
92
  power = _normalize_power(pov)
 
93
  try:
94
  q = int(qty) if qty is not None and str(qty).strip() != "" else 0
95
  except Exception:
@@ -98,8 +104,12 @@ def process(input_file):
98
  except Exception:
99
  q = 0
100
 
101
- if sku and power is not None and q:
102
- agg[(str(sku).strip(), power)] += q
 
 
 
 
103
 
104
  # --- OUTPUT: load template ---
105
  template_path = _download_template()
@@ -117,18 +127,16 @@ def process(input_file):
117
  triplet_row = None
118
  triplet_col_map = {}
119
 
120
- # First pass over top 10 rows to find labels
121
  for r in range(1, 11):
122
  row_vals = [ws_out.cell(row=r, column=c).value for c in range(1, ws_out.max_column + 1)]
123
 
124
  # (A) "MY SKU" detection
125
  for c, v in enumerate(row_vals, start=1):
126
  if isinstance(v, str) and v.strip().lower() == "my sku":
127
- # Treat this as the table header for the SKU column
128
  mysku_header_row = r
129
  mysku_col_idx = c
130
 
131
- # (B) textual power labels (prefer)
132
  labels = {}
133
  for c, v in enumerate(row_vals, start=1):
134
  if isinstance(v, str):
@@ -139,7 +147,7 @@ def process(input_file):
139
  power_label_row = r
140
  power_col_map = labels
141
 
142
- # (C) numeric triplets (fallback)
143
  trip = {}
144
  for c, v in enumerate(row_vals, start=1):
145
  if isinstance(v, str) and re.fullmatch(r"\d{2,3}", v.strip()):
@@ -161,13 +169,11 @@ def process(input_file):
161
  continue
162
  sku_to_row[str(val).strip()] = r
163
 
164
- # Optional top-area "MY SKU" label for summary list:
165
- # If there is ANOTHER "MY SKU" label in the top area (different from the table header row),
166
- # write unique SKUs to the cell to its right.
167
  summary_cell = None
168
  for r in range(1, 11):
169
  if r == mysku_header_row:
170
- continue # skip the table header "MY SKU"
171
  for c in range(1, ws_out.max_column + 1):
172
  v = ws_out.cell(row=r, column=c).value
173
  if isinstance(v, str) and v.strip().lower() == "my sku":
@@ -175,30 +181,52 @@ def process(input_file):
175
  break
176
  if summary_cell:
177
  break
178
- if summary_cell and agg:
179
- unique_skus = sorted({k[0] for k in agg.keys()})
180
- ws_out.cell(row=summary_cell[0], column=summary_cell[1]).value = ", ".join(unique_skus)
181
 
182
- # Write aggregated quantities
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  missing_skus = set()
184
  missing_powers = set()
185
- written_count = 0
186
 
187
  for (sku, power), qty in agg.items():
188
  row_idx = sku_to_row.get(sku)
189
  if row_idx is None:
190
  missing_skus.add(sku)
191
  continue
192
-
193
  col_idx = power_col_map.get(power) if power_col_map else None
194
  if col_idx is None and triplet_col_map:
195
  key = _power_to_triplet_digits(power)
196
  col_idx = triplet_col_map.get(key)
197
-
198
  if col_idx is None:
199
  missing_powers.add(power)
200
  continue
201
-
202
  current = ws_out.cell(row=row_idx, column=col_idx).value
203
  try:
204
  current_val = int(current) if current is not None and str(current).strip() != "" else 0
@@ -210,6 +238,39 @@ def process(input_file):
210
  ws_out.cell(row=row_idx, column=col_idx).value = current_val + int(qty)
211
  written_count += 1
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  # Save output
214
  tmpdir = tempfile.mkdtemp()
215
  out_path = os.path.join(tmpdir, "AMMU-order-form-FILLED.xlsx")
@@ -217,13 +278,14 @@ def process(input_file):
217
 
218
  log_lines = [
219
  f"Rows scanned in input: {rows_scanned}",
220
- f"Unique (SKU, power) pairs aggregated: {len(agg)}",
221
  f"Entries written into template: {written_count}",
 
222
  ]
223
  if missing_skus:
224
- log_lines.append(f"⚠️ SKUs not found in template ({len(missing_skus)}): {', '.join(sorted(missing_skus))}")
225
  if missing_powers:
226
- log_lines.append(f"⚠️ Powers not found in template ({len(missing_powers)}): {', '.join(sorted(missing_powers))}")
227
  log = "\n".join(log_lines)
228
 
229
  return out_path, log
@@ -232,14 +294,20 @@ def process(input_file):
232
  return None, f"Error: {e}"
233
 
234
  with gr.Blocks(title="AMMU Order Form Filler") as demo:
235
- gr.Markdown("### AMMU Order Form Filler\nUpload your input Excel. The app fills quantities into the AMMU template using **MY SKU** for row mapping and power columns for quantities.")
 
 
 
 
 
 
236
  with gr.Row():
237
  in_file = gr.File(label="Upload input Excel (.xlsx)", file_types=[".xlsx"])
238
  with gr.Row():
239
  run_btn = gr.Button("Process")
240
  with gr.Row():
241
  out_file = gr.File(label="Download filled template (.xlsx)")
242
- log_box = gr.Textbox(label="Log", lines=8)
243
 
244
  run_btn.click(fn=process, inputs=in_file, outputs=[out_file, log_box])
245
 
 
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)]
89
  sku = ws_in.cell(row=r, column=col_sku).value
90
  pov = ws_in.cell(row=r, column=col_pov).value
91
  qty = ws_in.cell(row=r, column=col_qty).value
 
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:
 
104
  except Exception:
105
  q = 0
106
 
107
+ entries.append({
108
+ "sku": (str(sku).strip() if sku is not None else None),
109
+ "power": power,
110
+ "qty": q,
111
+ "row_values": row_values
112
+ })
113
 
114
  # --- OUTPUT: load template ---
115
  template_path = _download_template()
 
127
  triplet_row = None
128
  triplet_col_map = {}
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):
 
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()):
 
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":
 
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:
232
  current_val = int(current) if current is not None and str(current).strip() != "" else 0
 
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")
 
278
 
279
  log_lines = [
280
  f"Rows scanned in input: {rows_scanned}",
281
+ f"Unique matched (SKU, power) pairs aggregated: {len(agg)}",
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
 
294
  return None, f"Error: {e}"
295
 
296
  with gr.Blocks(title="AMMU Order Form Filler") as demo:
297
+ gr.Markdown(
298
+ "### AMMU Order Form Filler\n"
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"])
306
  with gr.Row():
307
  run_btn = gr.Button("Process")
308
  with gr.Row():
309
  out_file = gr.File(label="Download filled template (.xlsx)")
310
+ log_box = gr.Textbox(label="Log", lines=10)
311
 
312
  run_btn.click(fn=process, inputs=in_file, outputs=[out_file, log_box])
313