Kndeh commited on
Commit
10f3406
Β·
1 Parent(s): 3188836

fix: filtering logics

Browse files
Files changed (2) hide show
  1. src/models/ai_engine.py +25 -4
  2. src/pages/1_AI_OCR.py +8 -3
src/models/ai_engine.py CHANGED
@@ -296,9 +296,19 @@ def is_valid_item(nm, price_str, cnt_str=None):
296
 
297
  price_raw_clean = str(price_str).strip()
298
  if len(re.findall(r'[a-zA-Z]', price_raw_clean)) > 3: return False
299
- if re.match(r'^[A-Z0-9\s\-:]{5,}$', nm_str) and not re.search(r'[a-z]', nm_str):
300
- if not re.search(r'(?i)(nasi|mie|ayam|ikan|teh|kopi|rice|chicken|coffee|tea|water|juice|beer|wine|pizza|burger|fries|soda|cake|spaghetti|alfredo|schnitzel|cordon|squash|poffertjes)', nm_str):
 
 
 
 
 
 
 
 
 
301
  return False
 
302
  return True
303
 
304
  # ===============================
@@ -352,9 +362,16 @@ def parse_cord_to_schema(cord_json):
352
  if isinstance(menu, dict): menu = [menu]
353
 
354
  for item in menu:
355
- entries = item if isinstance(item, list) else [item]
 
 
356
  for entry in entries:
357
- nm = entry.get("nm", "Unknown")
 
 
 
 
 
358
  price_raw = entry.get("price", "0")
359
  cnt_raw = entry.get("cnt", "1")
360
  if not is_valid_item(nm, price_raw, cnt_raw): continue
@@ -366,6 +383,8 @@ def parse_cord_to_schema(cord_json):
366
  sub_total_node = cord_json.get("sub_total", {})
367
  if isinstance(sub_total_node, list):
368
  sub_total_node = sub_total_node[0] if sub_total_node else {}
 
 
369
  subtotal = clean_price(sub_total_node.get("subtotal_price", "0"))
370
  tax_amount = clean_price(sub_total_node.get("tax_price", "0"))
371
  service_charge = clean_price(sub_total_node.get("service_price", "0"))
@@ -374,6 +393,8 @@ def parse_cord_to_schema(cord_json):
374
  total_node = cord_json.get("total", {})
375
  if isinstance(total_node, list):
376
  total_node = total_node[0] if total_node else {}
 
 
377
  total_amount = clean_price(total_node.get("total_price", "0"))
378
 
379
  items_sum = sum(i["item_price"] for i in items)
 
296
 
297
  price_raw_clean = str(price_str).strip()
298
  if len(re.findall(r'[a-zA-Z]', price_raw_clean)) > 3: return False
299
+
300
+ # Reject only obvious transaction codes / serial IDs:
301
+ # must be all-caps, contain digits, AND contain a separator like / or - or be >= 8 digits
302
+ if re.match(r'^[A-Z0-9\s\-:/\.]{5,}$', nm_str) and not re.search(r'[a-z]', nm_str):
303
+ digit_count = len(re.findall(r'\d', nm_str))
304
+ has_separator = bool(re.search(r'[/\-:]', nm_str))
305
+ # Only reject if it looks like a code: has digits AND a separator, OR is >50% digits
306
+ word_count = len(nm_str.split())
307
+ if digit_count > 0 and has_separator:
308
+ return False
309
+ if word_count <= 2 and digit_count > 0 and digit_count >= len(nm_str.replace(' ', '')) * 0.4:
310
  return False
311
+
312
  return True
313
 
314
  # ===============================
 
362
  if isinstance(menu, dict): menu = [menu]
363
 
364
  for item in menu:
365
+ if not isinstance(item, dict):
366
+ continue
367
+ entries = [item]
368
  for entry in entries:
369
+ if not isinstance(entry, dict):
370
+ continue
371
+ nm = entry.get("nm", None)
372
+ # Skip items with no name at all β€” don't accept "Unknown" fallback
373
+ if not nm or str(nm).strip() in ("", "Unknown"):
374
+ continue
375
  price_raw = entry.get("price", "0")
376
  cnt_raw = entry.get("cnt", "1")
377
  if not is_valid_item(nm, price_raw, cnt_raw): continue
 
383
  sub_total_node = cord_json.get("sub_total", {})
384
  if isinstance(sub_total_node, list):
385
  sub_total_node = sub_total_node[0] if sub_total_node else {}
386
+ if not isinstance(sub_total_node, dict):
387
+ sub_total_node = {}
388
  subtotal = clean_price(sub_total_node.get("subtotal_price", "0"))
389
  tax_amount = clean_price(sub_total_node.get("tax_price", "0"))
390
  service_charge = clean_price(sub_total_node.get("service_price", "0"))
 
393
  total_node = cord_json.get("total", {})
394
  if isinstance(total_node, list):
395
  total_node = total_node[0] if total_node else {}
396
+ if not isinstance(total_node, dict):
397
+ total_node = {}
398
  total_amount = clean_price(total_node.get("total_price", "0"))
399
 
400
  items_sum = sum(i["item_price"] for i in items)
src/pages/1_AI_OCR.py CHANGED
@@ -57,6 +57,7 @@ for k, v in {
57
  "tess_json": None,
58
  "preprocess_steps": [],
59
  "img_arr": None,
 
60
  }.items():
61
  if k not in st.session_state:
62
  st.session_state[k] = v
@@ -136,7 +137,7 @@ if img_arr is not None:
136
  st.session_state.preprocess_steps = steps
137
  st.session_state.donut_base_parsed = parsed
138
  st.session_state.donut_base_json = jout
139
- st.toast("βœ… Donut base extraction complete!", icon="🍩")
140
  except Exception as e:
141
  st.error(f"Donut base failed: {e}")
142
  with b3:
@@ -147,7 +148,7 @@ if img_arr is not None:
147
  st.session_state.preprocess_steps = steps
148
  st.session_state.donut_v6_parsed = parsed
149
  st.session_state.donut_v6_json = jout
150
- st.toast("βœ… Donut v6 extraction complete!", icon="🍩")
151
  except Exception as e:
152
  st.error(f"Donut v6 failed: {e}")
153
  with b4:
@@ -158,10 +159,14 @@ if img_arr is not None:
158
  st.session_state.preprocess_steps = steps
159
  st.session_state.tess_parsed = parsed
160
  st.session_state.tess_json = jout
161
- st.toast("βœ… Tesseract extraction complete!", icon="πŸ”€")
162
  except Exception as e:
163
  st.error(f"Tesseract failed: {e}")
164
 
 
 
 
 
165
  if st.session_state.preprocess_steps:
166
  st.subheader("Preprocessing Pipeline Visualization")
167
  cols = st.columns(min(len(st.session_state.preprocess_steps), 6))
 
57
  "tess_json": None,
58
  "preprocess_steps": [],
59
  "img_arr": None,
60
+ "last_success_msg": None,
61
  }.items():
62
  if k not in st.session_state:
63
  st.session_state[k] = v
 
137
  st.session_state.preprocess_steps = steps
138
  st.session_state.donut_base_parsed = parsed
139
  st.session_state.donut_base_json = jout
140
+ st.session_state.last_success_msg = "βœ… Donut Base extraction complete!"
141
  except Exception as e:
142
  st.error(f"Donut base failed: {e}")
143
  with b3:
 
148
  st.session_state.preprocess_steps = steps
149
  st.session_state.donut_v6_parsed = parsed
150
  st.session_state.donut_v6_json = jout
151
+ st.session_state.last_success_msg = "βœ… Donut v6 extraction complete!"
152
  except Exception as e:
153
  st.error(f"Donut v6 failed: {e}")
154
  with b4:
 
159
  st.session_state.preprocess_steps = steps
160
  st.session_state.tess_parsed = parsed
161
  st.session_state.tess_json = jout
162
+ st.session_state.last_success_msg = "βœ… Tesseract extraction complete!"
163
  except Exception as e:
164
  st.error(f"Tesseract failed: {e}")
165
 
166
+ if st.session_state.get("last_success_msg"):
167
+ st.success(st.session_state.last_success_msg)
168
+ st.session_state.last_success_msg = None
169
+
170
  if st.session_state.preprocess_steps:
171
  st.subheader("Preprocessing Pipeline Visualization")
172
  cols = st.columns(min(len(st.session_state.preprocess_steps), 6))